Due to some circumstances I wasn’t happy with my DNS setup, which was very basic I used Netlify DNS & as for Dynamic DNS, I used duckdns.org as a CNAME to a subdomain, which worked really well. My only gripe was downtimes with DuckDNS.
When searching for a Dynamic DNS solution I was a bit blown away by the subscription prices. Instead I decided to use my most comfortable cloud provider, that being Azure.
Requirements
To make sure there is none of that inevitable scope creep I wrote down a list of requirements that I need to fullfil.
Functional:
- One Stop Shop For all DNS management for domain.
- Dynamically update subdomain & wildcard subdomain records.
- Be able to dynamically set local IP Address using cron.
Non-Functional:
- Cheap!
- Easy to maintain // low-or-no-code.
Part 1 - General DNS Management.
This was simple, setting up Azure DNS Zones was straight forward with which I was able to quickly setup my website & email DNS records.
Part 2 - Dynamic DNS Mechanism.
Dynamic DNS is not provided by Azure :( But dynamic DNS is no voo-doo magic, it’s simply a DNS record with a low TTL(time to live). In simple terms if you manually update the entry on the Azure DNS portal with a low TTL that gets the job done.
But we don’t want to do that, I have seen a couple of DynDNS solutions using Azure Functions, but personally that felt too overkill. I was lazy and I need the easy way out! The Solution is Azure Logic Apps, Logic Apps are simply code-less event based functions that can be designed on the portal.
- Accepts HTTP Post Requests using a secured endpoint.
- Takes IP address given in request and updates Azure DNS subdomain A records.
- Responds with deployment status.
given below is the code view of my logic app. This is not directly compatible with another account, therefore this is to be used as a guide to configure the elements in the logic apps.
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"actions": {
"Respond_with_200": {
"inputs": {
"body": {
"status": "@{body('Update_DNS_Entries')?['properties']?['provisioningState']}",
"success": true
},
"headers": {
"Content-Type": "application/json; charset=utf-8"
},
"statusCode": 200
},
"kind": "Http",
"runAfter": {
"Update_DNS_Entries": [
"Succeeded"
]
},
"type": "Response"
},
"Update_DNS_Entries": {
"inputs": {
"body": {
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"destination_ip": {
"defaultValue": "@{triggerBody()?['ip']}",
"type": "String"
},
"dnszones_name": {
"defaultValue": "ukr.lk",
"type": "String"
}
},
"resources": [
{
"apiVersion": "2018-05-01",
"name": "[concat(parameters('dnszones_name'), '/home')]",
"properties": {
"ARecords": [
{
"ipv4Address": "[parameters('destination_ip')]"
}
],
"TTL": 600,
"targetResource": {}
},
"type": "Microsoft.Network/dnszones/A"
},
{
"apiVersion": "2018-05-01",
"name": "[concat(parameters('dnszones_name'), '/*.home')]",
"properties": {
"ARecords": [
{
"ipv4Address": "[parameters('destination_ip')]"
}
],
"TTL": 600,
"targetResource": {}
},
"type": "Microsoft.Network/dnszones/A"
}
],
"variables": {}
}
}
},
"host": {
"connection": {
"name": "@parameters('$connections')['arm']['connectionId']"
}
},
"method": "put",
"path": "/subscriptions/@{encodeURIComponent('<sub_id>')}/resourcegroups/@{encodeURIComponent('ukr-rg-person-dns-eastasia')}/providers/Microsoft.Resources/deployments/@{encodeURIComponent('update_ip')}",
"queries": {
"wait": true,
"x-ms-api-version": "2016-06-01"
}
},
"runAfter": {},
"type": "ApiConnection"
}
},
"contentVersion": "1.0.0.0",
"outputs": {},
"parameters": {
"$connections": {
"defaultValue": {},
"type": "Object"
}
},
"triggers": {
"manual": {
"inputs": {
"schema": {
"properties": {
"ip": {
"type": "string"
}
},
"type": "object"
}
},
"kind": "Http",
"type": "Request"
}
}
},
"parameters": {
"$connections": {
"value": {
"arm": {
"connectionId": "/subscriptions/<sub_id>/resourceGroups/ukr-rg-person-dns-eastasia/providers/Microsoft.Web/connections/arm",
"connectionName": "arm",
"id": "/subscriptions/<sub_id>/providers/Microsoft.Web/locations/eastasia/managedApis/arm"
}
}
}
}
}
Important lines to keep note (update based on your requirement),
- Line Number 38 - The Name of your DNS Zone as in the domain name.
- Line Number 45 & 59 - The DNS entry path, in this use-case the A record is updated to the
home
subdomain.
Part 3 - Updating records using cron.
With my router having no custom dynamic IP options, the task was on a Raspberry PI. This was fairly simple, I whipped up a bash script and then added it on to crontab.
Given below is the following script, please do change the webhook URL with your own.
#!/bin/bash
HIST_FILE=/tmp/dyn_ip_cur
WEB_HOOK="<enter webhook url>"
# Create Previous IP File if not present
if [ ! -f "$HIST_FILE" ]; then
echo "The file $HIST_FILE does not exist. Creating!"
echo "0.0.0.0" > $HIST_FILE
fi
# Fetch Previous IP From File
PRV_IP=$(cat $HIST_FILE)
# Get Current IP
CUR_IP=$(curl https://ip.42.pl/raw)
# Update Current IP to file
echo "$CUR_IP" > $HIST_FILE
echo "$PRV_IP -> $CUR_IP"
if [ $CUR_IP != $PRV_IP ];
then
echo "IP has updated to $CUR_IP , updating on Azure"
curl -L -X POST "$WEB_HOOK" \
-H 'Content-Type: application/json' \
--data-raw "{
\"ip\" : \"$CUR_IP\"
}"
fi
and in the crontab
*/5 * * * * /root/azuredns/az.sh >/dev/null 2>&1
Show Me The Money!
I got to emphasize, that this will depend on your usage & traffic. For my modest single user usage and not so pop wesbite I get around with $0.50 a month, which I think is pretty great!
Conclusion
Azure is great, in this case Logic Apps. I was able to archive all my functional and non-functional requirements! Hope this was helpful and I was able to nudge someone in the correct direction.