Introducing the HashiCorp Terraform Provider for VMware Cloud on AWS

Last updated on 2nd September 2020: Terraform VMC provider is automatically downloaded when running “terraform init” (no need to compile it – read further below for more details).

Here is a feature I had been hoping to see for for a long time – a Terraform provider to spin up VMware Cloud on AWS resources. This works at the ‘SDDC-level’ and enables customers to define a template of their VMC environment and essentially take the concept of Infrastructure-As-Code to the next level.

Just to reiterate – this is to create an entire VMware Cloud on AWS environment and not for vCenter constructs like virtual machines. Use the Terraform for vSphere for this use case.

There are many cool use cases that I can think of – automation, pop-up DCs for expansion, disaster recovery, etc… For example, I know of a couple of our customers in the education field who use VMware Cloud on AWS for online training and education. Terraform for VMware Cloud on AWS gives them the ability to stand up the same copy of their VMware Cloud environment for each training class. They can entirely automate the deployment of their SDDC on the day of the training class and spin it back down once the class is completed.

Combine this with Terraform with NSX and Terraform with vSphere and you can essentially ‘versionize’ and package your entire DC.

Mix it with the other flavours of VMware Cloud: VMware Cloud on Dell EMC and VMware Cloud on AWS Outposts – you could potentially get your hardware delivered on-site and get it all set up with a “terraform apply”…. but I’m getting carried away. Let’s review how to set this up with VMware Cloud on AWS.

The Terraform Provider for VMC is still being developed as we speak. I’ll update this post once the provider is completed.

Requirements:

Given that we need to manually download and build the provider, we need Git and Go installed. It will no longer be required once the provider is completed.

Update 2nd September 2020: The provider has been public for a while now so the steps described in “Installation” are no longer required. No need for Git or Go either. I am leaving in the blog for anyone who needs to understand how to build a provider from scratch. What this means is that, when you do “terraform init”, the provider will be automatically downloaded:

VMC provider automatically downloaded

Installation

As the provider is still a work-in-progress, you need to download the actual provider and compile it with Go for your own OS architecture. Once the provider is officially released, “terraform init” will automatically pulled the correct file for your environment and therefore this command will not be required. I will update this page accordingly once the provider is released.

First, we create a folder and move our shell prompt to that directory.

bash-3.2$ mkdir -p $HOME/development/terraform-providers/; cd $HOME/development/terraform-providers/

Then, we are going to manually pull the public code from GitHub using git clone and download and install GoLang packages and dependencies with “go get“.

bash-3.2$ git clone https://github.com/vmware/terraform-provider-vmc.git
Cloning into 'terraform-provider-vmc'...

remote: Enumerating objects: 47, done.
remote: Counting objects: 100% (47/47), done.
remote: Compressing objects: 100% (36/36), done.
remote: Total 4022 (delta 26), reused 29 (delta 11), pack-reused 3975
Receiving objects: 100% (4022/4022), 27.42 MiB | 7.85 MiB/s, done.
Resolving deltas: 100% (1026/1026), done.

bash-3.2$ go get
go: downloading github.com/hashicorp/terraform v0.12.13
go: downloading github.com/vmware/vsphere-automation-sdk-go/services/vmc v0.1.1
go: extracting github.com/vmware/vsphere-automation-sdk-go/services/vmc v0.1.1
go: extracting github.com/hashicorp/terraform v0.12.13
[.....]
go: finding github.com/mitchellh/hashstructure v1.0.0
go: finding github.com/bmatcuk/doublestar v1.1.5
bash-3.2$ 

Then, we can generate an executable binary with:

bash-3.2$ go build -o terraform-provider-vmc

You can move this file directly to your Terraform running folder or into the user plugins directory, located at %APPDATA%\terraform.d\plugins on Windows and ~/.terraform.d/plugins on other systems. See the official Terraform documentation for 3rd party plug-in.

Configuration

Next, you need an actual template where you define what you want to create:

provider "vmc" {
  refresh_token = "XXXXXXXXXXXXXXXXXXXXXXXXXXX"
}

data "vmc_org" "my_org" {
  id = "84e84f83-XXXX-XXXX-XXXX-XXXX"
}

data "vmc_connected_accounts" "my_accounts" {
  org_id = data.vmc_org.my_org.id
}

data "vmc_customer_subnets" "my_subnets" {
  org_id               = data.vmc_org.my_org.id
  connected_account_id = data.vmc_connected_accounts.my_accounts.ids[n]
  region               = "EU_WEST_2"
}

resource "vmc_sddc" "sddc_1" {
  org_id = data.vmc_org.my_org.id

  # storage_capacity    = 100
  sddc_name           = "TERRAFORM_SDDC"
  vpc_cidr            = "10.2.0.0/16"
  num_host            = 1
  provider_type       = "AWS"
  region              = data.vmc_customer_subnets.my_subnets.region
  vxlan_subnet        = "192.168.1.0/24"
  delay_account_link  = false
  skip_creating_vxlan = false
  sso_domain          = "vmc.local"

  # sddc_template_id = ""
  deployment_type = "SingleAZ"
  sddc_type       = "1NODE"
  account_link_sddc_config {
    customer_subnet_ids  = ["subnet-029a131dba7f47e24"]
    connected_account_id = data.vmc_connected_accounts.my_accounts.ids[n]
  }
  timeouts {
    create = "300m"
    update = "300m"
    delete = "180m"
  }
}

A best practice would be to define the variables in a separate variable files, using “variables.tf” and referring to them, using for example var.api_token or var.org_id to refer to variables. For now, I’m keeping it as simple as possible in this post and put the variables directly into the main.tf file.

Let’s review each field. You need to add the API token first. See instructions here on how to generate the token.

provider "vmc" {
  refresh_token = "XXXXXXXXXXXXXXXXXXXXXXXXXXX"
}

Add the org id in the second field. The org id can be found on the top-right hand corner (on the drop-down menu) or in the Support tab of your SDDCs.

data "vmc_org" "my_org" {
  id = "84e84f83-XXXX-XXXX-XXXX-XXXX"
}

We’re going to need to pull information from your VMC organization, such as the ID of your AWS accounts that are linked with VMware Cloud and the subnets that we link to during the creation of an SDDC. The assumption here is that we had already deployed an SDDC once and therefore linked to an AWS account.

At this stage, we’re going to keep it simple and assume you’ve only linked your SDDC with a single account, then the parameter below data.vmc_connected_accounts.my_accounts.ids would be a table with a single entry and therefore you can replace [n] below with [0]. I expect this process will be improved in the long run.

data "vmc_connected_accounts" "my_accounts" {
  org_id = data.vmc_org.my_org.id
}

data "vmc_customer_subnets" "my_subnets" {
  org_id               = data.vmc_org.my_org.id
  connected_account_id = data.vmc_connected_accounts.my_accounts.ids[n]
  region               = "EU_WEST_2"
}

Finally, the best bit: the actual resource we’re going to create: our VMware Cloud on AWS SDDC. Comments inline:

resource "vmc_sddc" "sddc_1" {
# We need to specify to the org we're going to deploy the SDDC in.
  org_id = data.vmc_org.my_org.id
# SDDC name
  sddc_name           = "TERRAFORM_SDDC"
# CIDR used for the Management Subnet of the VMC SDDC
  vpc_cidr            = "10.2.0.0/16"
# Number of hosts.
  num_host            = 1
# Where we're deploying the SDDC: in our case, the AWS cloud.
  provider_type       = "AWS"
# The region we specified above - for EU_WEST_2 (London).
  region              = data.vmc_customer_subnets.my_subnets.region
# The default subnet for the compute workloads.
  vxlan_subnet        = "192.168.1.0/24"
# Whether we're doing a Stretched Cluster "MultiAZ" or standard cluster.
  deployment_type = "SingleAZ"
# The type of SDDC - in our case, a single node SDDC. If you remove the line below, you will get a standard 3-node cluster (assuming you also update the num_hosts value above to 3.
  sddc_type       = "1NODE"
# The important bit - we specify the VPC and the AWS account we're connecting to. 
  account_link_sddc_config {
    customer_subnet_ids  = ["subnet-029a131dba7f47e24"]
    connected_account_id = data.vmc_connected_accounts.my_accounts.ids[0]
  }
  timeouts {
    create = "300m"
    update = "300m"
    delete = "180m"
  }
}

We can manually refer to the subnet-id (“subnet-029a131dba7f47e24”) in my deployment or we could have used the “vmc_customer_subnets” we created earlier.

Deployment

I always follow the same commands when using Terraform. Always start by initializing which will download the provider plugins or check we already have them. In our case, we have installed and put the ‘terraform-provider-vmc’ in the right folder and are ready to start.

bash-3.2$ terraform init

Initializing the backend...

Initializing provider plugins...

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.

Then, let’s validate the main.tf syntax with “terraform validate”:

bash-3.2$ terraform validate
Success! The configuration is valid.

We then plan what the resources would be deployed with “terraform plan”.

bash-3.2$ 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.

data.vmc_org.my_org: Refreshing state...
data.vmc_connected_accounts.my_accounts: Refreshing state...
data.vmc_customer_subnets.my_subnets: Refreshing state...

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

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:

  # vmc_sddc.sddc_1 will be created
  + resource "vmc_sddc" "sddc_1" {
      + cloud_password         = (known after apply)
      + cloud_username         = (known after apply)
      + cluster_id             = (known after apply)
      + delay_account_link     = false
      + deployment_type        = "SingleAZ"
      + host_instance_type     = "I3_METAL"
      + id                     = (known after apply)
      + nsxt_reverse_proxy_url = (known after apply)
      + num_host               = 1
      + org_id                 = "84e84f83-bb0e-4e12-9fe0-XXXXX"
      + provider_type          = "AWS"
      + region                 = "EU_WEST_2"
      + sddc_name              = "TERRAFORM_SDDC"
      + sddc_state             = (known after apply)
      + sddc_type              = "1NODE"
      + skip_creating_vxlan    = false
      + sso_domain             = "vmc.local"
      + vc_url                 = (known after apply)
      + vpc_cidr               = "10.2.0.0/16"
      + vxlan_subnet           = "192.168.1.0/24"

      + account_link_sddc_config {
          + connected_account_id = "284b27d6-52d7-3715-XXXXXX-XXXXXX"
          + customer_subnet_ids  = [
              + "subnet-029a131dba7f47e24",
            ]
        }

      + timeouts {
          + create = "300m"
          + delete = "180m"
          + update = "300m"
        }
    }

Plan: 1 to add, 0 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.

Once we’re ready, let’s go ahead with “terraform apply”:

bash-3.2$ terraform apply
data.vmc_org.my_org: Refreshing state...
data.vmc_connected_accounts.my_accounts: Refreshing state...
data.vmc_customer_subnets.my_subnets: Refreshing state...

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:

  # vmc_sddc.sddc_1 will be created
  + resource "vmc_sddc" "sddc_1" {
      + cloud_password         = (known after apply)
      + cloud_username         = (known after apply)
      + cluster_id             = (known after apply)
      + delay_account_link     = false
      + deployment_type        = "SingleAZ"
      + host_instance_type     = "I3_METAL"
      + id                     = (known after apply)
      + nsxt_reverse_proxy_url = (known after apply)
      + num_host               = 1
      + org_id                 = "84e84f83-bb0e-4e12-9fe0-XXXXX"
      + provider_type          = "AWS"
      + region                 = "EU_WEST_2"
      + sddc_name              = "TERRAFORM_SDDC"
      + sddc_state             = (known after apply)
      + sddc_type              = "1NODE"
      + skip_creating_vxlan    = false
      + sso_domain             = "vmc.local"
      + vc_url                 = (known after apply)
      + vpc_cidr               = "10.2.0.0/16"
      + vxlan_subnet           = "192.168.1.0/24"

      + account_link_sddc_config {
          + connected_account_id = "284b27d6-52d7-3715-XXXXXX-XXXXXX"
          + customer_subnet_ids  = [
              + "subnet-029a131dba7f47e24",
            ]
        }

      + timeouts {
          + create = "300m"
          + delete = "180m"
          + update = "300m"
        }
    }

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: yes

vmc_sddc.sddc_1: Creating...
vmc_sddc.sddc_1: Still creating... [10s elapsed]
vmc_sddc.sddc_1: Still creating... [20s elapsed]
vmc_sddc.sddc_1: Creation complete after 1h15m28s [id=96b0d14d-2e91-4f00-b3c9-xxxxxx]

About 75 minutes later, you get your SDDC up and running.

SDDC built by Terraform
SDDC built by Terraform

And in video, it looks like this:

Terraform for VMC – video

It gets interesting when you do a “terraform show”. We now display the URL of the NSX-T Reverse Proxy URL. This value is needed to make API calls to the NSX-T Policy Manager. We can take the output of this value and then use Terraform for NSX-T to create all sort of security and network constructs.

In other words, Terraform for VMC deploys the SDDC and once up and running, Terraform for NSX-T will create network segments, security rules, etc…

# vmc_sddc.sddc_1:
resource "vmc_sddc" "sddc_1" {
    cloud_password         = "tR6axToLA!5z+NT"
    cloud_username         = "cloudadmin@vmc.local"
    delay_account_link     = false
    deployment_type        = "SingleAZ"
    host_instance_type     = "I3_METAL"
    id                     = "94aea74f-b918-4c46-8273-086a6c39618b"
    nsxt_reverse_proxy_url = "https://nsx-3-11-207-231.rp.vmwarevmc.com/vmc/reverse-proxy/api/orgs/84e84f83-bb0e-4e12-9fe0-aaf3a4efcd87/sddcs/94aea74f-b918-4c46-8273-086a6c39618b/sks-nsxt-manager"
    num_host               = 1
    org_id                 = "84e84f83-bb0e-XXXX-XXXXX"
    provider_type          = "AWS"
    region                 = "EU_WEST_2"
    sddc_name              = "TERRAFORM_SDDC"
    sddc_state             = "READY"
    sddc_type              = "1NODE"
    skip_creating_vxlan    = false
    sso_domain             = "vmc.local"
    vc_url                 = "https://vcenter.sddc-3-11-207-231.vmwarevmc.com/"
    vpc_cidr               = "10.2.0.0/16"
    vxlan_subnet           = "192.168.1.0/24"

    account_link_sddc_config {
        connected_account_id = "XXXXXXX"
        customer_subnet_ids  = [
            "subnet-029a131dba7f47e24",
        ]
    }

    timeouts {
        create = "300m"
        delete = "180m"
        update = "300m"
    }
}

Resource Change

Where it gets even better is when you want to do some changes. Using Terraform’s idempotency and our ability to push infrastructure changes as code, you can quickly make some changes.

For example, if you want to move from 3 to 5 nodes, just change the value in our main.tf file from:

Before:
num_host            = 3
After
num_host            = 5

Run “terraform plan” and Terraform will realize that we only to make minor changes – in this case, the one highlighted above.

nvibert-a01:Terraform VMC nicolasvibert$ 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.

data.vmc_org.my_org: Refreshing state...
data.vmc_connected_accounts.my_accounts: Refreshing state...
data.vmc_customer_subnets.my_subnets: Refreshing state...
vmc_sddc.sddc_1: Refreshing state... [id=711aab27-c023-4083-a4fd-fb311616775e]

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

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:

  # vmc_sddc.sddc_1 will be updated in-place
  ~ resource "vmc_sddc" "sddc_1" {
        cloud_password      = "dummy_password"
        cloud_username      = "clouduser@vmc.local"
        delay_account_link  = false
        deployment_type     = "SingleAZ"
        host_instance_type  = "I3_METAL"
        id                  = "711aab27-c023-4083-a4fd-fb311616775e"
      ~ num_host            = 3 -> 5
        org_id              = "84e84f83-bb0e-4e12-9fe0-XXXXX"
        provider_type       = "AWS"
        region              = "EU_WEST_2"
        sddc_name           = "TERRAFORM_SDDC"
        sddc_state          = "READY"
        skip_creating_vxlan = false
        sso_domain          = "vmc.local"
        vpc_cidr            = "10.2.0.0/16"
        vxlan_subnet        = "192.168.1.0/24"

      + account_link_sddc_config {
          + connected_account_id = "284b27d6-52d7-3715-XXXXXX-XXXXXX"
          + customer_subnet_ids  = [
              + "subnet-029a131dba7f47e24",
            ]
        }

        timeouts {
            create = "300m"
            delete = "180m"
            update = "300m"
        }
    }

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.

“Terraform apply” adds two hosts in a matter of minutes to my SDDC.

Conclusion

As I mentioned throughout the article, it’s still early days and is still a work in development… But it’s public so you can check it out yourself. If you have any feedback, hit me and Gilles up on Twitter and we can feed that feedback straight to the developers.

Thanks for reading.