I like Terraform! 🙂
Had an idea the other day – I wanted to make myself an Ubuntu virtual machine on Microsoft Azure and also create a corresponding DNS record inside my kaniski.eu domain (currently hosted at CloudFlare… like, I guess, half of the Internet). Of course, I can do all that by using both of the portals, but this time I wanted to do it with Terraform, so it can be easily brought up and down, reusable, etc.
For this, you’ll need:
- Microsoft Azure subscription
- CloudFlare account (with a domain attached to its DNS servers, of course)
- Terraform (v0.13)
- (optional) Visual Studio Code
- (or you can all do it inside Azure Cloud Shell)
To configure Terraform with your Azure subscription, you’ll need:
- Azure Subscription ID
- Tenant ID
- Client ID
- Client secret
There’s a nice document about setting it all up – https://docs.microsoft.com/en-us/azure/developer/terraform/overview.
For accessing CloudFlare API via Terraform, you’ll need the following info from your CloudFlare account page:
- e-mail address
- CloudFlare zone ID
- CloudFlare account ID
- CloudFlare API key
Be really careful with using your Azure/CloudFlare keys (can’t be stressed enough!)!
Also note that the following code is only one way of doing it, one that suits me for this purpose, not best practice or the prettiest code ever! 🙂
The whole example (main.tf) looks like this (it can/should be improved, but that’s not really the point here and now):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# providers provider "azurerm" { version = "~> 2.24" subscription_id = var.subscription_id tenant_id = var.tenant_id client_id = var.client_id client_secret = var.client_secret features {} } provider "cloudflare" { version = "~> 2.10.0" email = var.cf_email api_key = var.cf_apikey account_id = var.cf_accountid } # resource group resource "azurerm_resource_group" "example-rg" { name = "example-rg" location = "West Europe" } # network resource "azurerm_virtual_network" "example-net" { name = "example-net" location = azurerm_resource_group.example-rg.location resource_group_name = azurerm_resource_group.example-rg.name address_space = ["10.11.0.0/16"] } # subnet resource "azurerm_subnet" "example-sub" { name = "example-sub" resource_group_name = azurerm_resource_group.example-rg.name virtual_network_name = azurerm_virtual_network.example-net.name address_prefixes = ["10.11.12.0/24"] } # nics resource "azurerm_network_interface" "myvm-nic" { name = "myvm-nic" location = azurerm_resource_group.example-rg.location resource_group_name = azurerm_resource_group.example-rg.name enable_ip_forwarding = true ip_configuration { name = "myvm-nic-ip-config" subnet_id = azurerm_subnet.example-sub.id private_ip_address_allocation = "Static" private_ip_address = "10.11.12.4" public_ip_address_id = azurerm_public_ip.myvm-ip.id } } # public ips resource "azurerm_public_ip" "myvm-ip" { name = "myvm-ip" location = azurerm_resource_group.example-rg.location resource_group_name = azurerm_resource_group.example-rg.name allocation_method = "Static" } # network security group resource "azurerm_network_security_group" "example-nsg" { name = "example-nsg" location = azurerm_resource_group.example-rg.location resource_group_name = azurerm_resource_group.example-rg.name security_rule { name = "SSH" priority = 100 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" destination_port_range = "22" source_address_prefix = "*" destination_address_prefix = "10.11.12.4" } } resource "azurerm_subnet_network_security_group_association" "example-sub-nsg-assoc" { subnet_id = azurerm_subnet.example-sub.id network_security_group_id = azurerm_network_security_group.example-nsg.id } # vms resource "azurerm_virtual_machine" "myvm" { name = "myvm" location = azurerm_resource_group.example-rg.location resource_group_name = azurerm_resource_group.example-rg.name network_interface_ids = [azurerm_network_interface.myvm-nic.id] vm_size = "Standard_DS1_v2" delete_os_disk_on_termination = true storage_image_reference { publisher = "Canonical" offer = "UbuntuServer" sku = "19_10-daily-gen2" version = "latest" } storage_os_disk { name = "myvm-osdisk" caching = "ReadWrite" create_option = "FromImage" managed_disk_type = "Standard_LRS" } os_profile { computer_name = "myvm" admin_username = var.admin_username } os_profile_linux_config { disable_password_authentication = true ssh_keys { key_data = file("~/.ssh/id_rsa.pub") path = "/home/${var.admin_username}/.ssh/authorized_keys" } } } # cloudflare dns resource "cloudflare_record" "myvm-dns" { zone_id = var.cf_zoneid name = "myvm" value = azurerm_public_ip.myvm-ip.ip_address type = "A" } |
Variables used in this simple example are (terraform.tfvars):
1 2 3 4 5 6 7 8 9 |
subscription_id = "" tenant_id = "" client_id = "" client_secret = "" admin_username = "" cf_zoneid = "" cf_email = "" cf_apikey = "" cf_accountid = "" |
Just note one more thing – I’m using the current version of Terraform (v0.13) and version 2 of the CloudFlare provider (notable changes between the v1 and v2 are listed at https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/guides/version-2-upgrade).
As always, running terraform init will take care of the missing providers and install them as needed. Also, running terraform plan can give us an estimation of what exactly will be done and terraform fmt will make our code look nice! 🙂
When I run this code (terraform apply), it completes successfully (of course) and provisions 9 items and, more important – gives me info about the DNS name and IP address of the provisioned VM (which I already know, but still):
- DNS record really exists in CloudFlare DNS?
- resources are really provisioned in Azure?
- SSH into the VM works?
And that’s it – when you’re done, you can use terraform destroy and it’s all gone! 🙂
Cheers!