Nutanix : Create a simple VM with Terraform

Background

In today's world, we are always looking at automation. Automation is key to provide a certain level of self-service. With Nutanix, you can automate many things in many ways (Calm, X-Play, API scripts ...). But if you don't have the resources nor the money for this, there is another player on the market

Meet Terraform
Terraform is an open-source infrastructure as code software tool created by HashiCorp. It enables users to define and provision a datacenter infrastructure using a high-level configuration language known as Hashicorp Configuration Language, or optionally JSON.

At least, this is what Wikipedia is saying about it. You can either run Terraform in the cloud or on-prem (on your laptop for example). There are providers for many different infrastructure like AWS, Azure, GitHub, OVH, ... and Nutanix. Those providers are maintained either by the community or the vendors themselves.

Installation for macOS

I'm using brew to install terraform, it is maybe the easiest way.

MacBook-Air:~$ brew install terraform
Updating Homebrew...
==> Homebrew has enabled anonymous aggregate formulae and cask analytics.
Read the analytics documentation (and how to opt-out) here:
  https://docs.brew.sh/Analytics
  
==> Downloading https://homebrew.bintray.com/bottles/terraform-0.12.19.catalina.bottle.tar.gz
==> Downloading from https://akamai.bintray.com/7f/7f8a1371bc9786efd3aec4401c37a45b5f19dced3c62150cc97cf5e93a24817f?__gda__=exp=157
######################################################################## 100.0%
==> Pouring terraform-0.12.19.catalina.bottle.tar.gz
🍺  /usr/local/Cellar/terraform/0.12.19: 6 files, 51.0MB
==> `brew cleanup` has not been run in 30 days, running now...

Removing: /usr/local/Cellar/gettext/0.19.8.1... (1,934 files, 16.9MB)

Once completed, terraform is ready to be used : 

MacBook-Air:~$ terraform -v
Terraform v0.12.19

Now, we can create our first terraform test file using the Nutanix provider :

provider "nutanix" {
username = "xxxxxxxxxxx"
password = "xxxxxxxxxxx"
endpoint = "prism_central_ip"
insecure = true
}

resource "nutanix_virtual_machine" "MyTestVM_TF" {
name = "MyTestVM-TF"
description = "Created with Terraform"
provider = nutanix
cluster_uuid = "000512b5-c10d-f271-0000-000000005f0a"
  num_vcpus_per_socket = 1
  num_sockets = 1
  memory_size_mib = 2048
 
  nic_list {
    # subnet_reference is saying, which VLAN/network do you want to attach here?
    subnet_uuid = "e8b383f2-fecd-43b9-ade6-a0563884f96a"
  }

  disk_list {
# data_source_reference in the Nutanix API refers to where the source for
# the disk device will come from. Could be a clone of a different VM or a
# image like we're doing here
data_source_reference = {
kind = "image"
uuid = "1eb9ed98-3c07-44be-ac05-bad9f42ef86d"
  }

device_properties {
  disk_address = {
device_index = 0
adapter_type = "IDE"
  }

  device_type = "DISK"
}
  }

  disk_list {
# defining an additional entry in the disk_list array will create another.
#disk_size_mib and disk_size_bytes must be set together.
disk_size_mib   = 100000
disk_size_bytes = 104857600000
  }
}  
  
output "ip_address" {
  value = nutanix_virtual_machine.MyTestVM_TF.nic_list_status.0.ip_endpoint_list[0]["ip"]
}

Save this file as anyname.tf and place it in a specific folder. Terraform is looking at any file within the current folder.

In the above example, you see some Uuid for the Cluster, the VLAN and the disk image. Those Uuid can be extracted from acli (I'm using AHV). You can either run net.list or image.list to get the right value. The cluster Uuid can be obtained with the UI.

Next, we need to be sure that terraform has all the relevant providers installed. Let's initialize it !

MacBook-Air:~$ terraform init

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "nutanix" (terraform-providers/nutanix) 1.0.2...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.nutanix: version = "~> 1.0"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

At this stage, you are good to go.

So, terraform works in three differents stages : plan, apply destroy.

  • Plan is actually checking the config and connectivity to the various elements in your terraform files.
  • Apply is actually doing the job
  • Destroy is an amazing roll-back functionality that revert back to anything that has been done in the Apply mode. Of course, the Destroy stage is not mandatory but nice to have !

Let's plan the above script :

MacBook-Air:~$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

nutanix_virtual_machine.MyTestVM_TF: Refreshing state... [id=d95998a9-cddc-4703-bb46-fffb41a526e1]

------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # nutanix_virtual_machine.MyTestVM_TF will be updated in-place
  ~ resource "nutanix_virtual_machine" "MyTestVM_TF" {
        api_version                                      = "3.1"
        availability_zone_reference                      = {}
        boot_device_disk_address                         = {}
        boot_device_order_list                           = []
        cluster_name                                     = "XXXXXXX"
        cluster_uuid                                     = "000512b5-c10d-xxx-0000-000000005f0a"
        description                                      = "Created with Terraform"
        enable_script_exec                               = false
        guest_customization_cloud_init_custom_key_values = {}
        guest_customization_is_overridable               = false
        guest_customization_sysprep                      = {}
        guest_customization_sysprep_custom_key_values    = {}
        hardware_clock_timezone                          = "UTC"
[...]

Plan: 0 to add, 1 to change, 0 to destroy.

------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

The above output is a big long, so I have truncated it, but you got the overall idea.

Now that we do not have any error, let's "Apply" :

MacBook-Air:~$ terraform apply
nutanix_virtual_machine.MyTestVM_TF: Refreshing state... [id=d95998a9-cddc-4703-bb46-fffb41a526e1]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # nutanix_virtual_machine.MyTestVM_TF will be created
  + resource "nutanix_virtual_machine" "MyTestVM_TF" {
      + api_version                                      = (known after apply)
      + availability_zone_reference                      = (known after apply)
      + boot_device_disk_address                         = (known after apply)
      + boot_device_mac_address                          = (known after apply)
      + boot_device_order_list                           = (known after apply)
      + cluster_name                                     = (known after apply)
      + cluster_uuid                                     = "000512b5-xxxx-xxxx-0000-000000005f0a"
      + description                                      = "Created with Terraform"
      + enable_script_exec                               = (known after apply)
      + guest_customization_cloud_init_custom_key_values = (known after apply)
      + guest_customization_cloud_init_meta_data         = (known after apply)
      + guest_customization_cloud_init_user_data         = (known after apply)
      + guest_customization_is_overridable               = (known after apply)
      + guest_customization_sysprep                      = (known after apply)
      + guest_customization_sysprep_custom_key_values    = (known after apply)
      + guest_os_id                                      = (known after apply)
      + hardware_clock_timezone                          = (known after apply)
      + host_reference                                   = (known after apply)
      + hypervisor_type                                  = (known after apply)
      + id                                               = (known after apply)
      + memory_size_mib                                  = 2048
      + metadata                                         = (known after apply)
      + name                                             = "MyTestVM-TF"
      + ngt_credentials                                  = (known after apply)
      + ngt_enabled_capability_list                      = (known after apply)
      + nic_list_status                                  = (known after apply)
      + num_sockets                                      = 1
      + num_vcpus_per_socket                             = 1
      + num_vnuma_nodes                                  = (known after apply)
      + nutanix_guest_tools                              = (known after apply)
      + owner_reference                                  = (known after apply)
      + parent_reference                                 = (known after apply)
      + power_state                                      = (known after apply)
      + power_state_mechanism                            = (known after apply)
      + project_reference                                = (known after apply)
      + should_fail_on_script_failure                    = (known after apply)
      + state                                            = (known after apply)
      + vga_console_enabled                              = (known after apply)

      + categories {
          + name  = (known after apply)
          + value = (known after apply)
        }

[...]

        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:

Enter yes to confirm (if you know what you are doing, you can use terraform apply -no-approve statement) :

Enter a value: yes

nutanix_virtual_machine.MyTestVM_TF: Creating...
nutanix_virtual_machine.MyTestVM_TF: Still creating... [10s elapsed]
nutanix_virtual_machine.MyTestVM_TF: Creation complete after 18s [id=7e2ec1c8-a5dd-4033-bb0c-63fe0cc48a3c]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

ip_address = 192.168.8.102

The VM has been created on the Nutanix cluster :



Now, this VM can be deleted using the destroy mode. Or, you can create new terraform files to modify it ...

All the Nutanix provider documentation is on the terraform website and on github. Have fun !

Note : I've spent a lot of time by trying to connect my terraform files to the Prism Element of my cluster. It appears that you will have better results by connecting to Prism Central instead.



Comments

What's hot ?

ShredOS : HDD degaussing with style

Nutanix : CVM stuck into Phoenix