Azure Linux VM with DNS
Managing DNS Records with Azure DNS
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:
- update records for your domain (e.g.
example.com
) directly with GoDaddy DNS servers - create a subdomain, e.g.
dev.example.com
, and have Azure DNS manage records for your subdomain, e.g.dev.example.com
. - 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:
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
- Azure Provider: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs
- CloudFlare: https://github.com/cloudflare/terraform-provider-cloudflare
- GoDaddy: https://github.com/n3integration/terraform-provider-godaddy
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.