Deploying Multiple Resources with count in Terraform

Hi!

It took me some time to publish something new. I’ve been studying, and I disconnected for some time to spend time with the family.

I’m back now, with a lot of energy and eager to share with you what’s been going on.

I just finished the Cloudskills Cloud Native DevOps Bootcamp, and I had a blast! We had ten weeks of great content, taught by the best instructors in this field. This bootcamp gave us a solid understanding of the DevOps practices, processes and tools. Things like source control, scripting, testing, infrastructure-as-code, CI/CD, containers, security and much more. If you’re thinking about starting a new career as as DevOps engineer, like I am, then I cannot recommend this bootcamp enough!

My idea then is to share here a bit of what happened in these ten weeks. I tried to document as best as I could my learning journey, and I want to share some of the highlights here. I intend to make the articles short and simple. I hope you can learn something from these articles, and I’d love to hear from you.

For this first article, I’ll talk about Terraform and how to use this very interesting feature, the count argument, to deploy a number of equal resources at once.

Terraform

Terraform is an amazing tool. You can do a lot with it, and there are lots of useful commands that can make your life easier. Terraform is my tool of choice to deploy infrastructure to the cloud. It is cloud agnostic, meaning you can use it with all the major cloud providers. Terraform uses its own language, called HashiCorp Configuration Language (HCL). It is similar to JSON, but easier to understand.

For this article, I’ll show you how to deploy 2 Ubuntu virtual machines to Azure using Terraform. This was part of one of the hands-on labs we did for the bootcamp. There were other labs that used Terraform, and I’d like to mention them too in future articles. For this specific lab, they didn’t specify any particular way to deploy the resources. The only specification was that 2 Ubuntu virtual machines of equal characteristics were needed. I figured I could give it a try with Terraform, and I wanted to try to use the count argument. With count, you can deploy several similar objects without the need to write separate blocks for each one.

Deploying 2 similar VMs with count

Count can be used with modules and with every resource type. It goes inside the resource block and takes whole numbers. Terraform will create as many instances of the resource as the number specified in count.

This is the code I used to create two identical Ubuntu VMs.

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "azurerg" {
  name     = var.resource_group_name
  location = var.location
}

resource "azurerm_virtual_network" "azvnet" {
  name                = "cloudskills_vnet"
  address_space       = ["10.0.0.0/22"]
  location            = azurerm_resource_group.azurerg.location
  resource_group_name = azurerm_resource_group.azurerg.name
}

resource "azurerm_subnet" "internal" {
  name                 = "internal"
  resource_group_name  = azurerm_resource_group.azurerg.name
  virtual_network_name = azurerm_virtual_network.azvnet.name
  address_prefixes     = ["10.0.2.0/24"]
}

resource "azurerm_network_interface" "main" {
  count               = 2
  name                = "UbuntuNIC-${count.index}"
  resource_group_name = azurerm_resource_group.azurerg.name
  location            = azurerm_resource_group.azurerg.location

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.internal.id
    private_ip_address_allocation = "Dynamic"
  }
}

resource "azurerm_linux_virtual_machine" "LinuxVM" {
  count                           = 2 
  name                            = "UbuntuVM-${count.index}"
  resource_group_name             = azurerm_resource_group.azurerg.name
  location                        = azurerm_resource_group.azurerg.location
  size                            = "Standard_D2S_v3"
  admin_username                  = var.username
  admin_password                  = var.password
  disable_password_authentication = false
  network_interface_ids = [azurerm_network_interface.main.*.id[count.index],]

  source_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"
  }

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }
}

In the first block, I’m telling Terraform to create me a resource group. As you can see, I’m using variables. I’m referring to these variables in another file, the variables.tf. The variable block can be anywhere, but it’s highly recommended that you place them in a separate file called variables.tf. The values themselves are set in a file called terraform.tfvars. Besides the resource group name and the location, I also set the username and password for the VM as variables. For these, I added the sensitive argument, and I set it to True. Like that, these values won’t be displayed in the terraform plan and terraform apply outputs. But they still appear in the state file. So be careful. You should always keep your state file in a safe place. I wrote an article about it some time ago.

Now, for what really matters, the count argument, or meta-argument, as HashiCorp calls it.

This is the block where we create the network interface.

resource "azurerm_network_interface" "main" {
  count               = 2
  name                = "UbuntuNIC-${count.index}"
  resource_group_name = azurerm_resource_group.azurerg.name
  location            = azurerm_resource_group.azurerg.location

Terraform will create 2 NICs, as defined by count. For the name, we use the ${count.index} syntax. We use this one, with the curly brackets, when we want to use the count index in a string, such as in a name. As you all know, indexes start at 0 (zero). In our case, Terraform will create two NICs, one called UbuntuNIC-0, and the second one called UbuntuNIC-1.

This is the VM block.

resource "azurerm_linux_virtual_machine" "LinuxVM" {
  count                           = 2 
  name                            = "UbuntuVM-${count.index}"
  resource_group_name             = azurerm_resource_group.azurerg.name
  location                        = azurerm_resource_group.azurerg.location
  size                            = "Standard_D2S_v3"
  admin_username                  = var.username
  admin_password                  = var.password
  disable_password_authentication = false
  network_interface_ids = [azurerm_network_interface.main.*.id[count.index],]

For the VM name, it’s the same as above, for the NIC. Now, I want to call your attention to the network_interface_ids argument. Here, we use a different syntax, with square brackets, because we are referencing the resources created previously, in the order they were created.

Is it clear? It took me some time to figure this one out. I had the help of the great Derek Morgan, from the More than Certified in Terraform fame. This is a course I can’t recommend enough! The course is not just great, with lots of content, but Derek is a great guy, always willing to help and to answer our questions. I love it!

I almost forgot! As it was just a development and learning environment, I used the same username and password for both VMs. If I were to generate different usernames and password for every instance created with count, I’d maybe try the random_string resource. It generates a random string of characters. But I don’t know if it’s a good practice, or if it’s safe, to generate sensitive information with this technique. I know that with Python’s random library, it is not recommended. I’ll have to do some research. Do you know if it’s safe? Please let me know.

The count meta-argument is very useful, and it saves us a lot of time. Try to use it yourself in your deployments. And tell me how it went.

See you next time, with more Terraform!

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s