Cloud Automation: Autoregistering Virtual Machines with Private DNS Zones in Oracle OCI using Serverless Functions

Published by Max Jahn on

What you should know: I am currently working for Oracle as Principal Cloud Architect, but any posts on this blog reflect my own views and opinions only.

In one of my earlier posts i already showed how private and hybrid DNS is working in Oracle OCI. If you are not familiar with this topic i recommend to take a look at this post first.

The solution i described for private and hybrid DNS in OCI involves using a custom DNS resolver that will replace the default behavior. This default behavior gives you one nice advantage: you do not actually have to care much about DNS records of VMs in your VCN as you get name resolving within that VCN for free. You just need to get accustomed with the naming scheme hostname.subnetname.vcnname.oraclevcn.com.

Once you go for the custom resolver, this will not work any longer. So you have to take care of creating, updating and deleting DNS A records yourself.

Approaches

Basically there are 3 approaches for this:

  1. Manually manage the records in the Web Console, via CLI or API: Not. Much. Fun.
  2. Have strong governance in place for the whole lifecycle of a VM: Creating VMs, changing IP addresses and destroying of a VM must be handled via tools like terraform or ansible only. Then you can do the updates to the DNS zone records whenever your tools do those changes. Probably will work perfectly well in controlled environments, but break for more volatile development playgrounds. But that’s ok in most cases.
  3. Use cloud events and serverless functions to act on events that signal creation, update or termination of a virtual machine. The function then will be responsible for keeping the records of your private DNS Zone up to date.

My recommendation would be to stick to a combination of strategies 2 and 3. I would go for terraform in controlled environments like production and QA. But for more volatile environments like development i would set up a serverless function to do this job.

As usual i added my code to a github repo. To install a demo environment in the Frankfurt region you can use a Oracle Resource Manager stack i created and put in the release directory.
If you are using a different region, you first need to import the serverless function image to your OCI Container Registry (OCIR). Your can do this with the fn_import_auto_image.sh helper script which assumes that you got a working docker, jq and OCI CLI installation.

Automation via Terraform

The first approach i want to cover is to add another resource for DNS records in your terraform scripts. I will assume that you got your DNS Zone created and DHCP options configured as described in my post on OCI DNS. Then basically all you need to do in your terraform script to add a section like this for each VM you created.

resource "oci_dns_record" "myvm_record" {
    zone_name_or_id = oci_dns_zone.private_dns_zone.id
    domain = "myvm_name.my.dns.zone"
    rtype = "A"
    rdata = oci_core_instance.myvm.private_ip
    ttl = 30
}
Automation via Cloud Events & Serverless Functions

The second approach uses events to trigger a serverless function to update DNS records.

The first element in my implementation is the use of defined tags. The tags will be used for configuring the DNS records to be associated with a virtual machine.

Then there are cloud events and rules that will trigger a serverless function. This function then will query some metadata and update DNS records in the private DNS zone.

This post will cover how this approach can be implemented.

Environment

First we need to have the environment set up. I assume that a DNS zone has been created and DHCP options to use the custom resolver are in place.

Serverless Function Environment

To setup your environment for serverless functions you can simply take a look at my blog post on this topic. After the setup you should have

  • OCI Policy for FaaS applied to the tenancy,
  • Users and Groups set up,
  • a Dynamic Group for the functions being executed,
  • a OCI Functions Application created (suggested name automation-app) and
  • the FN CLI set up.

Additionally you need to add OCI policies to allow your function to make changes to a DNS zone.

  allow dynamic-group fn-dyn-grp to manage dns-family in compartment id YOUR_COMPARTMENT
  allow dynamic-group fn-dyn-grp to use all-resources in compartment id YOUR_COMPARTMENT

Tags

Another element of the proposed approach is using defined tags to add some metadata to the resources that are managed in the cloud. These will indicate if changes to the resources lead to a serverless function being executed.

So you need to create a tag namespace and define two tags to use for the DNS zone and DNSHostname. By default my example implementation assumes that the namespace Automation and defined tags DNSZone and DNSHostname will be used.

Serverless Function

I implemented the serverless function using the Go FDK for Oracle Functions. The way it works is dead simple:

  1. Get the cloud event data from context and extract the DNS zone and hostname. As well determine from the event type if a record needs to be added or removed.
  2. Read metadata of VM and its associated VNICs using the OCI API.
  3. For the default (and most common) case determine the primary IP of the primary VNIC.
  4. Patch the DNS zone record with the data gathered so far using OCI API.
Additional Features
Using your own Tags

Then there is some extra magic in case you decided to use different Tags for your resources. In this case the values passed in the following environment variables are used:

  • OCI_DNS_TAG_NAMESPACE (default: Automation)
  • OCI_DNS_TAG_ZONE (default: DNSZone)
  • OCI_DNS_TAG_HOSTNAME (default: DNSHostname)

The environment variables can be set in the Web Console, via OCI CLI, via FN CLI or in terraform.

Multiple Hostnames for Virtual Machines

And then there is the case when you have a host that got multiple VNICs and multiple private IP addresses. Then you simply do not only tag the VM for its primary IP address, but every private IP address.

Defaults for Hostname

The function will be executed if the DNSZone tag has a value. If a DNSHostname value has not been given, the function will use the virtual machine hostname instead.

Installing the Function

The easiest way to get the function installed is to go to the function source directory and use the FN CLI.

fn -v deploy -app automation-app

Or you can pull my image from dockerhub and push it to the OCI container registry. Then deploy the image as function via OCI CLI.

docker pull maxjahn/event-dns-autoregister
docker tag maxjahn/event-dns-autoregister:latest REGION.ocir.io/NAMESPACE/fn-automation/event-dns-autoregister:latest
docker push YOUR_REGION.ocir.io/YOUR_NAMESPACE/fn-automation/event-dns-autoregister:latest

oci fn application list  \
| jq '.data[] | select(."display-name"=="automation-app") | .id' -r | \
xargs oci fn function create --display-name "dns-autoregister" \
--image YOUR_REGION.ocir.io/YOUR_NAMESPACE/fn-automation/event-dns-autoregister:latest \
--memory-in-mbs 128 --application-id

Adding Cloud Events

Now that the function to update the DNS records has been created, we need to take a look at how to use cloud events to trigger actions.

Create a rule for that will match events with event types

  • com.oraclecloud.computeapi.launchinstance.end
  • com.oraclecloud.computeapi.updateinstance
  • com.oraclecloud.computeapi.terminateinstance.begin

and that are emitted by resources tagged with a defined tag DNSZone. So if this tag is missing, no further processing should happen.

Event rules can easily be created via OCI CLI, Web Console or terraform. For the example i will use the CLI. First get the OCID of the function that has been created before and assign it to an environment variable.

export OCI_FN_ID=`oci fn application list --compartment-id YOUR_COMPARTMENT_OCID \
| jq '.data[] | select(."display-name"=="automation-app") | .id' -r | \
xargs oci fn function list --compartment-id COMPARTMENT_OCID --application-id | \ 
jq '.data[] | select(."display-name"=="event-dns-autoregister") | .id' -r`

Then create the rule with the CLI.

oci events rule create --compartment-id YOUR_COMPARTMENT_OCID \
--display-name "autoregister_private_dns" \
--condition "{\"eventType\":[\"com.oraclecloud.computeapi.terminateinstance.begin\",\"com.oraclecloud.computeapi.updateinstance\",\"com.oraclecloud.computeapi.launchinstance.end\"],\"data\":{\"definedTags\":{\"Automation\":{\"DNSZone\":\"*\"}}}}" \
--is-enabled true \
--actions '{ "actions": [{"actionType": "FAAS", "description": "process in fn", "functionId": "'${OCI_FN_ID}'", "isEnabled": true }]}'

This is how it should look like in your environment then.

Give the Setup a Try

Now that everything is set up, we can try if it works. Just create a new instance in the compartment and tag it accordingly.

After some time (might take a minute) you should see you new instance being registered with your DNS zone.

And when the instance gets terminated, it should get removed from the DNS records automatically.

Conclusion

Even though it is nice that other cloud providers like Microsoft Azure have autoregistration capabilities out of the box, you can build similar functionalities easily yourself. Cloud events and serverless functions are great tools to enable automation tasks.