Azure Linux VM with DNS

Managing DNS Records with Azure DNS

Joaquín Menchaca (智裕)
9 min readJun 16, 2021

--

After creating a system with a public IP, you can add a friendly DNS name to reference this such as appvm.example.com.

Terraform can automatically create or update DNS records for many services: Azure DNS, AWS Route53, Google Cloud DNS, GoDaddy, CloudFlare, etc.

Once you purchased a domain name, such as through a service like GoDaddy, you have a few options for automating DNS:

  1. update records for your domain (e.g. example.com) directly with GoDaddy DNS servers
  2. create a subdomain, e.g. dev.example.com, and have Azure DNS manage records for your subdomain, e.g. dev.example.com.
  3. update records for your domain, e.g. example.com, using Azure DNS

DISCLAIMER: I do not endorse the GoDaddy service; it is merely here as for example purposes.

Requirements

Registered domain name

As this tutorial uses a public domain name, so you will need to purchase this from somewhere to follow along with this tutorial. This generally costs about $2 to $20 per year.

For demonstration purposes, we’ll use a fictional domain of example.com, and a fictional subdomain of dev.example.com.

Tool requirements

  • Azure CLI tool (az): command line tool that interacts with Azure API
  • Terraform (terraform): command line tool to provision cloud resources easily (tested with Terraform 1.0)
  • jq (jq) [optional]: JSON processor that makes JSON output more readable

Previous article

This article will leverage from a previous article. This will provision a basic Linux VM with a public IP address.

If you do not wish to use or already have a server with a public IP address, you can supply the public IP address as input to the necessary modules used in this article.

File Structure from the previous article

If you ran through the previous article, you should have the following file structure:

.
├── azure_net
│ ├── main.tf
│ ├── outputs.tf
│ ├── provider.tf
│ └── variables.tf
├── azure_vm
│ ├── main.tf
│ ├── network.tf
│ ├── outputs.tf
│ ├── provider.tf
│ └── variables.tf
├── azure_vm.pem
├── main.tf
├── outputs.tf
├── terraform.tfvars
└── variables.tf

Variable definition from the previous article

In the previous article, the terraform.tfvars file included the following:

resource_group_name = "devapp"
location = "westus"
image_publisher = "Canonical"
image_offer = "0001-com-ubuntu-server-focal"
image_sku = "20_04-lts"
computer_name = "appvm"
admin_username = "azureuser"

Steps from the previous article

If you have not yet created the resource group and Linux VM with the public IP address, you can do that now with the following commands:

az group create --location westus --resource-group devapp
terraform apply --target module.azure_net --target module.azure_vm

After a few minutes, you should have a public IP address, which you can verify with:

terraform refresh && terraform output --raw public_ip

You will also need to save the private SSH key for access:

terraform output --raw tls_private_key > azure_vm.pem
chmod 400 azure_vm.pem

Scenario A: GoDaddy managed domain

In this scenario, the domain is managed by GoDaddy DNS server. We will use the public API from GoDaddy to update the records using Terraform.

Part 1: API Key

Before accessing the GoDaddy public API, you need to generate a Production API key:

When you have these, create these environment variables in an env.sh file that we can source later:

export GODADDY_API_KEY="<your-api-key>"
export GODADDY_API_SECRET="<your-api-secret>"
export TF_VAR_domain="<your-domain-name>" # example.com

Now either run these commands individually or run source source env.sh.

Part 2: GoDaddy Module Structure

We will create a small module for managing GoDaddy records. The Terraform directory structure will look like the following with GoDaddy module parts emboldened:

.
├── azure_net
│ ├── main.tf
│ ├── outputs.tf
│ ├── provider.tf
│ └── variables.tf
├── azure_vm
│ ├── main.tf
│ ├── network.tf
│ ├── outputs.tf
│ ├── provider.tf
│ └── variables.tf
├── azure_vm.pem
├── env.sh
├── godaddy_dns_record
│ ├── main.tf
│ └── provider.tf
├── main.tf
├── outputs.tf
├── terraform.tfvars
└── variables.tf

In Bash or Zsh, you can create this with the following:

mkdir godaddy_dns_record
touch godaddy_dns_record/{main,provider}.tf

Part 3: GoDaddy DNS record module

As this is a small module used to create a single DNS record, we will just have two files a provider.tf and main.tf.

The provider will require setting the GODADDY_API_KEY and GODADDY_API_SECRET environment variables that you created earlier.

Copy this below and save as godaddy_dns_record/provider.tf:

Copy this below and save as godaddy_dns_record/main.tf:

Part 4: Using the module

From the previous article, the modules azure_net and azure_vm created a Linux VM with a public_ip address. This resulting public_ip along with the computer_name will be passed into this module.

Modify main.tf to have utilize the new module:

We’ll need to add a variable for the domain, such as example.com. to the variables.tf:

Part 5: Running the module

In a previous step, you should have defined environment variables for your API keys with GODADDY_API_KEY and GODADDY_API_SECRET, and also your domain name that you registered as TF_VAR_domain. In Bash (or any POSIX shell), if these were saved in env.sh, source that now.

source env.sh

When the public_ip has been assigned, you can create the DNS A record:

terraform apply --target module.godaddy_dns_record

Verify that the record is created with:

URL="https://api.godaddy.com/v1/domains/${TF_VAR_domain}/records/A"
AUTH="Authorization: sso-key $GODADDY_API_KEY:$GODADDY_API_SECRET"
curl --silent --request GET $URL \
--header "accept: application/json" --header $AUTH | jq .[]

Part 6: Test the Results

Now test logging into the system using the domain name:

ssh azureuser@appvm.${TF_VAR_domain} -i ./azure_vm.pem

Part 7: Cleanup

When finished, you will need to remove record created with Terraform through the GoDaddy DNS Manager UI. Other resources can be removed with the following:

terraform destroy --target module.azure_net --target module.azure_vm
chmod +w azure_vm.pem && rm azure_vm.pem
az group delete devapp

Scenario B: Azure DNS subdomain

In this scenario, we’ll create a subdomain prefixed with dev in Azure DNS. Thus if we had a domain in GoDaddy of example.com, we’d create a subdomain of dev.example.com.

Part 1: Modules file structures

From the previous article, the modules azure_net and azure_vm created a Linux VM with a public_ip address. This resulting public_ip along with the computer_name will be passed into this module.

We will add two small modules, an azure_dns_domain module to create the subdomain, and an azure_dns_record to create a record in that subdomain.

In Bash or Zsh we can create the file structure with:

mkdir azure_dns_{domain,record}
touch azure_dns_{domain,record}/{main,provider}.tf

The resulting structure with new items emboldened will look like this:

.
├── azure_dns_domain
│ ├── main.tf
│ └── provider.tf
├── azure_dns_record
│ ├── main.tf
│ └── provider.tf
├── azure_net
│ ├── main.tf
│ ├── outputs.tf
│ ├── provider.tf
│ └── variables.tf
├── azure_vm
│ ├── main.tf
│ ├── network.tf
│ ├── outputs.tf
│ ├── provider.tf
│ └── variables.tf
├── azure_vm.pem
├── main.tf
├── outputs.tf
├── terraform.tfvars
└── variables.tf

Part 2: Subdomain module

Copy the following and save as azure_dns_domain/provider.tf:

Copy the following and save as azure_dns_domain/main.tf:

Part 3: Record module

Copy the following and save as azure_dns_record/provider.tf:

Copy the following and save as azure_dns_record/main.tf:

Part 4: Using the modules

Modify main.tf to have utilize two new modules:

The variables will need to be updated have a domain and subdomain_prefix variable:

Part 5: Create the subdomain

The subomdain will be created with the following command:

export TF_VAR_domain="<your-domain-name-goes-here>"
terraform
apply --target module.azure_dns_domain

Part 6: Update GoDaddy to use Azure DNS for the subdomain

To use the subdomain, GoDaddy DNS server will have to point to Azure DNS servers to resolve subdomain records, e.g. dev.example.com. This can be done by adding NS records.

This can be done by using GoDaddy DNS web console:

Find your domain and make the entries like these below for subdomain prefixed with dev. The Azure DNS name server addresses will vary:

ns-01.azure-dns.com

NOTE: This process cannot be automated using GoDaddy’s public API, and it is something they do not care to support and have no interest in supporting it in the future. Only their internal v2 API supports this, which can be accessed through Godaddy DNS web console.

Part 7: Register new address record in the subdomain

terraform apply --target module.azure_dns_record

Part 8: Test the Results

Now test logging into the system using the subdomain name:

export TF_VAR_domain="<your-domain-name-goes-here>"
ssh
azureuser@appvm.dev.${TF_VAR_domain} -i ./azure_vm.pem

Part 9: Cleanup

When finished, you can remove the resources with the following:

terraform destroy --target module.azure_dns_domain
terraform destroy --target module.azure_dns_record
terraform destroy --target module.azure_net --target module.azure_vm
chmod +w azure_vm.pem && rm azure_vm.pem
az group delete devapp

Scenario 2: Update records in Azure DNS

In this scenario we will transfer DNS control to Azure DNS and then update records on Azure DNS.

Part 1: API Key

Similar to Scenario A, before accessing the GoDaddy public API, you need to generate a Production API key:

When you have these, create these environment variables in an env.sh file that we can source later:

export GODADDY_API_KEY="<your-api-key>"
export GODADDY_API_SECRET="<your-api-secret>"
export TF_VAR_domain="<your-domain-name>" # example.com

Now either run these commands invidually or run source source env.sh.

Part 2: Modules file structures

From the previous article, the modules azure_net and azure_vm created a Linux VM with a public_ip address. This resulting public_ip along with the computer_name will be passed into this module.

In addition to three small modules:

  • azure_dns_domain: creates the zone to manage the domain (same module as Scenario B)
  • azure_dns_record: creates a single record (same module as Scenario B)
  • godaddy_dns_nameservers: points nameservers for the domain to Azure DNS

In Bash or Zsh we can create the file structure with:

mkdir azure_dns_{domain,record}
touch azure_dns_{domain,record}/{main,provider}.tf \
godaddy_dns_nameservers/{main,provider}.tf

The resulting structure with new items emboldened will look like this:

.
├── azure_dns_domain
│ ├── main.tf
│ └── provider.tf
├── azure_dns_record
│ ├── main.tf
│ └── provider.tf
├── azure_net
│ ├── main.tf
│ ├── outputs.tf
│ ├── provider.tf
│ └── variables.tf
├── azure_vm
│ ├── main.tf
│ ├── network.tf
│ ├── outputs.tf
│ ├── provider.tf
│ └── variables.tf
├── azure_vm.pem
├── env.sh
├── godaddy_dns_nameservers
│ ├── main.tf
│ └── provider.tf

├── main.tf
├── outputs.tf
├── terraform.tfvars
└── variables.tf

Part 3: Domain module

Copy the following and save as azure_dns_domain/provider.tf:

Copy the following and save as azure_dns_domain/main.tf:

Part 4: DNS Record module

Copy the following and save as azure_dns_record/provider.tf:

Copy the following and save as azure_dns_record/main.tf:

Part 5: GoDaddy NS module

Copy the following and save as godaddy_dns_nameservers/provider.tf:

Copy the following and save as godaddy_dns_nameservers/main.tf:

Part 6: Using the modules

Modify main.tf to have utilize three new modules:

The variables will need to be updated have a domain variable:

Part 7: Create the domain

The subdomain will be created with the following command:

export TF_VAR_domain="<your-domain-name-goes-here>"
terraform
apply --target module.azure_dns_domain

Part 8: Domain transfer to Azure DNS

source env.sh
export TF_VAR_domain=
"<your-domain-name-goes-here>"
terraform
apply --target module.azure_dns_domain

Part 9: Register new address record in the domain

terraform apply --target module.azure_dns_record

Part 10: Test the Results

Now test logging into the system using the subdomain name:

export TF_VAR_domain="<your-domain-name-goes-here>"
ssh
azureuser@appvm.${TF_VAR_domain} -i ./azure_vm.pem

Part 11: Cleanup

When finished, you can remove the resources with the following:

terraform destroy --target module.azure_dns_domain_record
terraform destroy --target module.azure_dns_domain
terraform destroy --target module.azure_net --target module.azure_vm
chmod +w azure_vm.pem && rm azure_vm.pem
az group delete devapp

In the GoDaddy DNS Manager UI, the nameservers will have to be reset back state where GoDaddy DNS sever manages the domain. Strangely, this requires extra layer of security, where you will have to verify confirmation for through an external source.

Resources

Providers

Source Code

Conclusion

I hope this can be a useful guide to start you on the journey to automate DNS records along with provisioning services on Azure. as well as general modularization patterns with Terraform. This shows how you might go about using either an external DNS solution, Azure DNS, or a mixture of the two.

For applications that do not change frequently, likely you will not need to update primary domain names through automation. However for applications that change frequently, such as test and stage environments, automating DNS can be useful, especially for creating self-service test environments.

For securing web applications with certificates, having a publicly registered domain name is the first step required.

--

--

Joaquín Menchaca (智裕)

DevOps/SRE/PlatformEng — k8s, o11y, vault, terraform, ansible