Recently, I attempted to use the Ohai value for node['cloud_v2']['local_ipv4']
and node['cloud']['local_ipv4']['ip_address']
to determine the Private IP address of my Cloud-based nodes in a Chef cookbook. Unfortunately, it does not work accurately for DigitalOcean instances any longer.
According to DigitalOcean documentation, if Private Networking is enabled, the IP will be assigned to eth1
. Recently, I noticed that a second Private IP address has begun to be assigned to the eth0
interface. This is/was causing Ohai to assign the eth0
secondary (private) IP address to node['cloud_v2']['local_ipv4']
and node['cloud']['local_ipv4']['ip_address']
As you can see below, there is a second, private IP address assigned to eth0. I believe this has something to do with the recent release of Floating IP Addresses.
bdwyertech@dummy-droplet:~$ cat /etc/network/interfaces
# This file describes the network interfaces available on your
# system and how to activate them. For more information, see
# interfaces(5).
# The loopback network interface
auto lo
iface lo inet loopback
# The primary network interface
auto eth1 eth0
iface eth0 inet static
address 123.234.123.234
netmask 255.255.255.0
gateway 123.234.123.1
up ip addr add 10.13.0.123/16 dev eth0
dns-nameservers 8.8.8.8 8.8.4.4
iface eth1 inet static
address 10.128.123.123
netmask 255.255.0.0
Initially, I just wrote a simple function to detect the IP address on eth1
via the node hash if DigitalOcean is detected by Ohai as the cloud provider. However, querying DigitalOcean metadata seems to be the more robust solution.
DigitalOcean has released a metadata service, similar to AWS, where you can query http://169.254.169.254/metadata/{API_VERSION}
for droplet information. DigitalOcean conveniently allows you to query the droplet’s metadata in its entirety and return it in JSON format. I’ve went ahead and written a simple library to query this data and bring it into Ruby as a hash.
# DigitalOcean Metadata Chef Library
# rubocop:disable LineLength
require 'net/http'
# Public: This defines a module to retrieve Metadata from DigitalOcean
module DoMetadata
DO_METADATA_ADDR = '169.254.169.254' unless defined?(DO_METADATA_ADDR)
DO_SUPPORTED_VERSIONS = %w( v1 )
DO_DEFAULT_API_VERSION = 'v1'
def self.http_client
Net::HTTP.start(DO_METADATA_ADDR).tap { |h| h.read_timeout = 600 }
end
# Get metadata for a given path and API version
def metadata_get(id, api_version = DO_DEFAULT_API_VERSION, json = false)
path = "/metadata/#{api_version}/#{id}"
path = "/metadata/#{api_version}.json" if json
response = http_client.get(path)
case response.code
when '200'
response.body
when '404'
Chef::Log.info("Encountered 404 response retreiving DO metadata path: #{path} ; continuing.")
nil
else
fail "Encountered error retrieving DO metadata (#{path} returned #{response.code} response)"
end
end
module_function :metadata_get
# Retrieve the JSON metadata, and return it as a Ruby hash
def parse_json_metadata(api_version = DO_DEFAULT_API_VERSION)
retrieved_metadata = metadata_get(nil, api_version, true)
return unless retrieved_metadata
JSON.parse(retrieved_metadata) if retrieved_metadata
end
module_function :parse_json_metadata
end
Code also available on my Github.
This conveniently allows you to query the resulting Ruby hash, and use it in your code.
# => Get the Droplet's Metadata
metadata = DoMetadata.parse_json_metadata
metadata['interfaces']['private'][0]['ipv4']['ip_address'] # => Droplet's Private IP Address
metadata['interfaces']['private'][0]['ipv4']['netmask'] # => Droplet's Private Subnet Mask