Terraform Modules

In the previous section, we successfully deployed a complete ACI fabric. But we leveraged a single plan where every resource was included in the file. What about if I want to break my code into sub-modules (functions) and have the ability to re-use the code. This is where Modules can play an important role in your application development.

From the Terraform documentation, "A module is a container for multiple resources that are used together. Modules can be used to create lightweight abstractions, so that you can describe your infrastructure in terms of its architecture, rather than directly in terms of physical objects."

In order to showcase the power of modules, we will be creating and deploying the following new objects:

  1. Tenant
  2. VRF
  3. Bridge Domains

It is very important to create the Directories under the rigth path and add the files under the correct directories

Step 1 - Create the directory structure

It is very important during this step to follow the directions because we need to create the right directory structure. We will be creating 4 directories:

  1. aci_tenant
  2. bd
  3. tenant
  4. vrf

The first step will be to created some root level directories.


mkdir ~/terraform/mod_examples

Now that we have the top of the tree mod_example created you need to create the modules directory and the primary module that will be aci_tenant


mkdir ~/terraform/mod_examples/modules
mkdir ~/terraform/mod_examples/aci_tenant

Under the modules directory is where the three modules bd,tenant and vrf will be located


mkdir ~/terraform/mod_examples/modules/bd
mkdir ~/terraform/mod_examples/modules/tenant
mkdir ~/terraform/mod_examples/modules/vrf

This directory structure should be visible in the IDE GUI. To assist you with the files in this module, let's create all the files needed directly in the CLI so they are visible to you on the GUI immediately


touch ~/terraform/mod_examples/aci_tenant/main.tf
touch ~/terraform/mod_examples/modules/bd/bd.tf
touch ~/terraform/mod_examples/modules/bd/variables.tf
touch ~/terraform/mod_examples/modules/tenant/tenant.tf
touch ~/terraform/mod_examples/modules/tenant/variables.tf
touch ~/terraform/mod_examples/modules/vrf/vrf.tf
touch ~/terraform/mod_examples/modules/vrf/variables.tf

Step 2 - Edit the Main File

Under the terraform/mod_examples/aci_tenant, edit main.tf. This file is the file that will be invoking the Terraform> modules itself. Think of it as the parent terraform file.


provider "aci" {
    username              = "admin"
    password              = "cisco.123"
    url                   = "http://10.0.226.41"
    insecure              = true
    }

module "my_tenant" {
    source                = "../modules/tenant"
    tenant                = "mod_pod03"
    }

module "my_vrf" {
    source                = "../modules/vrf"
    vrf                   = "vrf_pn_03"
    tenant_id             = module.my_tenant.tenant_id
    }

module "my_bd_app" {
    source                = "../modules/bd"
    bd                    = "pod03_app"
    ip                    = "5.1.1.1/24"
    tenant_id             = module.my_tenant.tenant_id
    vrf_id                = module.my_vrf.vrf_id
    }

module "my_bd_web" {
    source                = "../modules/bd"
    bd                    = "pod03_web"
    ip                    = "6.1.1.1/24"
    tenant_id             = module.my_tenant.tenant_id
    vrf_id                = module.my_vrf.vrf_id
    }   

Step 3 - Edit the Bridge Domain Module Terraform file

Now you will be able to see the value of modules. Let's suppose that you have a standard set of configuration options that you want your Bridge Domains in ACI to contain across the board. Via Terraform it is possible to define all these specific values as you see fit to your needs and by invoking the module, it will create those configuration options consistently.

For this example there are some things that we want to keep consistent across these bridge domains unless specified otherwise. These will be:

  1. arp_flood:yes - We are going to always want flooding to be true.
  2. unicast_route:yes - We want to use IP based forwarding instead of mac
  3. unk_mac_ucast_act:yes - We want unknown destinations to be flooded

Under the terraform/mod_examples/bd, modify bd.tf

  
resource "aci_bridge_domain" "bd" {
    tenant_dn             = var.tenant_id
    arp_flood             = var.arp_flood
    unicast_route         = var.unicast_route
    unk_mac_ucast_act     = var.unkunicast_route
    relation_fv_rs_ctx    = var.vrf_id
    name                  = var.bd
    }

resource "aci_subnet" "bd_subnet" {
    parent_dn             = aci_bridge_domain.bd.id
    ip                    = var.ip
    }    

Now via the structure of the modules, if nothing is defined in the top requesting terraform file, it will assume the defaults that we are setting in the variable file.

Step 4 - Edit the Bridge Domain Module Variable file

Under the terraform/mod_examples/bd, modify variables.tf. Here you can see that we are creating the default values for how we want the bridge domains to be built. You will notice in the top module we have not defined these values. So terraform will take the default values and apply them.

This is an easy way to create consistency in how you want these bridge domains configured. You can override these default values by defining in the top main.tf file the values that you would want. We will accomplish this below.

     
variable "tenant_id" {
    default = ""
    }

variable "vrf_id" {
    default = ""
    }

variable "bd" {
    default = ""
    }

variable "ip" {
    default = ""
    }

variable "arp_flood" {
    default = "yes"
}

variable "unicast_route" {
    default = "yes"
}

variable "unkunicast_route" {
    default = "flood"
}


Step 5 - Edit the Tenant Module Terraform file

Under the terraform/mod_examples/tenant, modify tenant.tf

      
resource "aci_tenant" "tenant" {
    name                  = var.tenant
    }

output "tenant_id" {
    value = "${aci_tenant.tenant.id}"
    }   

Step 6 - Create the Tenant Module Variable file

Under the terraform/mod_examples/tenant, create the following file variables.tf

      
variable "tenant" {
    default = ""
    }

variable "tenant_id" {
    default = ""
    }
    

Step 7 - Create the VRF Module Terraform file

Under the terraform/mod_examples/vrf, create the following file vrf.tf

     
resource "aci_vrf" "vrf" {
    tenant_dn             = var.tenant_id
    name                  = var.vrf
    }

output "vrf_id" {
    value = "${aci_vrf.vrf.id}"
    }   

Step 8 - Create the VRF Module Variable file

Under the terraform/mod_examples/vrf, create the following file variables.tf

   
variable "tenant_id" {
    default = ""
    }

variable "vrf" {
    default = ""
    }

variable "vrf_id" {
    default = ""
    }

Step 9 - Initialize the Project

Initialize the project, this process will download the necessary plugins which will allow Terraform to interact with vSphere.


cd ~/terraform/mod_examples/aci_tenant
terraform init

labuser@terra-vm-pod03:~/terraform/mod_examples/aci_tenant$ terraform init
Initializing modules...
- my_bd_app in ../modules/bd
- my_bd_web in ../modules/bd
- my_tenant in ../modules/tenant
- my_vrf in ../modules/vrf

Initializing the backend...

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

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.aci: version = "~> 0.3"

Step 10 - Apply the final configuration to ACI

After successfully initialized Terraform, the next step is to execute the terraform plan and terraform apply.


    terraform plan -out main.plan
    terraform apply "main.plan"

labuser@terra-vm-pod03:~#  terraform plan -out main.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.


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

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


 labuser@terra-vm-pod03:~# terraform apply "main.plan"
module.my_tenant.aci_tenant.tenant: Creating...
module.my_tenant.aci_tenant.tenant: Creation complete after 1s [id=uni/tn-mod_pod03]
module.my_vrf.aci_vrf.vrf: Creating...
module.my_vrf.aci_vrf.vrf: Creation complete after 3s [id=uni/tn-mod_pod03/ctx-pn_03]

Step 11 - Modify one bridge domain to different settings

Previously we showed you how to use default values in specific configurations so that you can keep consistency in how you want to configure objects. In this step we will make a modification to one of the bridge domains such that we tell Terraform that we want to deviate from our default for a specific configuration.

Back to the main.tf terraform file that contains the calls for all the modules. You are going to modify the file to a specific change. You are going to tell Terraform that you wish to tell the bridge domain to not flood ARP's or unknown unicast. You are going to enter these values for this bridge and Terraform knows now to override it's default with the value entered.


provider "aci" {
    username              = "admin"
    password              = "cisco.123"
    url                   = "http://10.0.226.41"
    insecure              = true
    }

module "my_tenant" {
    source                = "../modules/tenant"
    tenant                = "mod_pod03"
    }

module "my_vrf" {
    source                = "../modules/vrf"
    vrf                   = "vrf_pn_03"
    tenant_id             = module.my_tenant.tenant_id
    }

module "my_bd_app" {
    source                = "../modules/bd"
    bd                    = "pod03_app"
    ip                    = "5.1.1.1/24"
    arp_flood             = "no"
    unkunicast_route      = "proxy"
    tenant_id             = module.my_tenant.tenant_id
    vrf_id                = module.my_vrf.vrf_id
    }

module "my_bd_web" {
    source                = "../modules/bd"
    bd                    = "pod03_web"
    ip                    = "6.1.1.1/24"
    tenant_id             = module.my_tenant.tenant_id
    vrf_id                = module.my_vrf.vrf_id
    }   

Now run the terraform plan command.


terraform plan -out main.plan

You will notice that Terraform now shows that it will do a modification:

 labuser@terra-vm-pod03:~# terraform apply "main.plan"
[CUT]
~ resource "aci_bridge_domain" "bd" {
    ~ arp_flood = "yes" -> "no"
    [CUT]
    ~ unk_mac_ucast_act = "flood" -> "proxy"
}
Plan: 0 to add, 1 to change, 0 to destroy.

Now you can execute the apply command to push these new changes to ACI.


terraform apply "main.plan"

And in the fabric only the one Bridge Domain will see the modified policies applied. As you can see this can provide for great structured way to code the policies in the fabric with a default set of values that you would like to use and at the same time provide the capability to change these when needed.