Let’s Terraform the vTM: Part 3 / 4

Let’s Terraform the vTM: Part 3 / 4

Carrying on from Part 1 and Part 2, in this instalment we’ll continue adding to our configuration.

If you’re back with us after a break – feel free to go over the part 1 and 2 again, and if you’re following along – make sure your set-up is all good, and the very last exercise from Part 2 completes correctly.

In the last exercise we’ve added one pool that can be populated with any number of nodes by supplying IP:Port for those nodes through a variable. Since the second pool isn’t any different, simply add the following to your main.tf, variables.tf, and terraform.tfvars, and re-run terraform apply to see if it works:

# main.tf
#
data "vtm_pool_nodes_table_table" "api_pool_nodes" {
  count    = "${length(var.api_nodes)}"
  node     = "${var.api_nodes[count.index]}"
  priority = "1"
  state    = "active"
  weight   = "1"
}

resource "vtm_pool" "api_pool" {
  name             = "${local.uniq_id}_API-Pool"
  monitors         = ["Ping"]
  nodes_table_json = "[${join(",", data.vtm_pool_nodes_table_table.api_pool_nodes.*.json)}]"
}

# variables.tf
#
variable "api_nodes" {
  description = "List of nodes for the 'API' pool"
  default     = []
}

# terraform.tfvars
api_nodes = ["192.168.1.100:80", "192.168.2.100:80"]

 

If all went well, your vTM will now have two pools with two nodes in each.

So, all these resources..

You probably been wondering about where you can find all the possible choices you have for vTM resources in your templates. Those familiar with vTM’s REST API have likely noticed that the resource names look suspiciously like vTM REST resources with vtm_ slapped in front, with resource parameters very much a 1:1 transplant.

And you will be right – the resources (and data sources) that vTM Provider exposes closely match those in the REST API. There are a few minor changes where internal Terraform variables clashed with parameter names in vTMs REST schema and have been slightly changed to aviod it. In addition, however, vTM Provider exposes a number of special data sources: *_list and *_table. You’ll meet them in this blog post very soon. 🙂

The complete set of resources, data sources, and useful examples can be found in the vTM Terraform Provider documentation (REST API v4.0 / vTM 17.2 and later) or (REST API v5.2 / vTM 18.1 and later).

Traffic IP Groups and conditions

You may wish to have a template that works in different environments; e.g., when your vTM cluster is running in AWS, you may be using Elastic IP (EIP) based Traffic IP Group (TIG), or when you’re running in a Docker container for a dev/test task, you may not want to create a TIG at all. Let’s add some logic to our template as an example for how you could handle this.

First, let’s create a variable vtm_tig_eips in variables.tf:

variable "vtm_tig_eips" {
  description = "List of AWS Elastic IPs to be used for Traffic IP Group"
  default     = []
}

 

As you see, by default we set this variable to be an empty list, which suits our playpen environment. Now let’s add the code to out main.tf that will create a Traffic IP Group in case our list had actual AWS EIPs in it:

# This returns a list populated with "name" values of all traffic managers
# in the target cluster. We need this to create the Traffic IP Group.
#
data "vtm_traffic_manager_list" "cluster_machines" {
  # No parameters needed
}

locals {
  tig_name = "${local.uniq_id}_TrafficIPGroup"
}

# Traffic IP Group for our Virtual Server.
# By default, we create the "singlehosted" type.
#
resource "vtm_traffic_ip_group" "tip_group" {
  count       = "${signum(length(var.vtm_tig_eips))}"
  name        = "${local.tig_name}"
  mode        = "ec2vpcelastic"
  ipaddresses = "${var.vtm_tig_eips}"
  machines    = ["${data.vtm_traffic_manager_list.cluster_machines.object_list}"]
}

 

The above introduces a couple points worth noticing.

First, we use Data Source vtm_traffic_manager_list to get the list of vTMs in a cluster we’re working on. We need to know this as it’s a required parameter machines for the vtm_traffic_ip_group resource. Since this lookup is dynamic, this code will work on vTM clusters of any size, including a single-node.

This is an example of one of the special resource types (*_list) provided by the vTM Terraform Provider.

Second, we use count as a condition to create the resource. The length(var.vtm_tig_eips) will return a number of IPs supplied in the variable, or 0. We’re good for when it returns a 0, which will cause this resource to not be created; but we don’t want more than one Traffic IP Group in case our variable had multiple IPs in it! This is where Terraform’s built-in function signum() comes handy – it will keep 0 a 0, and convert any positive number into 1, which is what we want.

Since we have not included any IPs for our vtm_tig_ips, running terraform plan should say that no changes are required.

Last but not least, we defined a name for our TIP Group as a Local Variable. The need for this will become clear in a few moments.

Let’s test if our logic works. The configuration may not be valid, but at least we’ll know it was applied. 🙂 Add the following to our terraform.tfvars, and run terraform apply:

vtm_tig_eips = ["2.2.2.2", "5.5.5.5"]

 

Your vTM’s log should display a Warning “VPC Elastic IP addresses can only be configured on EC2-VPC instances”, and Services -> Traffic IP Groups should have a broken TIP Group with our two IP addresses “2.2.2.2” and “5.5.5.5”.

Let’s undo this for now. Edit the terraform.tfvars, put a # in front of the vtm_tig_eips line, and then re-run terraform apply. The TIP Group should disappear from your vTM.

Ok, let’s add our Virtual Server (VS) before winding up for today. To start with, let’s add a simple HTTP VS. We’ll add SSL Offload and L7 routing to it in our next part.

Edit your main.tf and add the following block:

locals {
  # If var.vtm_tig_eips is empty, we should not use TIP Group
  should_listen_on_any = "${length(var.vtm_tig_eips) == 0 ? true : false}"

  # This is to provide the list of TIP Group Names to listen_on_traffic_ips
  # paremeter of the Virtual Server. If We don't have any Traffic IPs, this
  # list should be empty; if we do - it should have a name of our TIP Group.
  tigs_list = ["${length(var.vtm_tig_eips) == 0 ? "" : local.tig_name}"]
}

# The Virtual Server
#
resource "vtm_virtual_server" "vs1" {
  name          = "${local.uniq_id}_VS1"
  enabled       = "true"
  listen_on_any = "${local.should_listen_on_any}"

  # We need to use compact() to get rid of values "" which will be there
  # in case we didn't have any TIP Groups
  listen_on_traffic_ips = ["${compact(local.tigs_list)}"]

  # Default pool = "Main"
  pool     = "${vtm_pool.main_pool.name}"
  port     = "80"
  protocol = "http"
}

 

Since we’re working around two cases here – we either do or don’t have Traffic IP Group – we need to make sure our Virtual Server has correct values for the related parameters: listen_on_any and listen_on_traffic_ips. The code above first defines a couple Local Variables that are set based on condition, and then uses them as values for the respective parameters of the VS1.

listen_on_traffic_ips deserves a special mention. For it to not take effect when we don’t use TIPs, the value it receives must be an empty list – []. However the best we can do with the tigs_list variable is to set it to a list that has one empty element, [""], which isn’t good enough. Luckily for us, compact() can get rid of the empty element, and we’re off to races!

Now, why did we define a local variable to hold a name for our TIP Group? This is to handle the case where the vtm_tig_ips is empty, and the resource "vtm_traffic_ip_group" "tip_group" is not created. Usually we would refer to the .name parameter of our resource to fetch what we need; but what do we do if the resource creation is conditional?

Unfortunately at the moment Terraform will always evaluate both sides of a condition. This means we can’t just create a logic that says “if there are Traffic IPs, create TIP Group and get the name from it; otherwise don’t create a group and set the name to []”. If we did, Terraform would always try to evaluate both, and fail in the case where we don’t have any TIPs specified.

So the work-around for that in our specific case is to create a name for our TIP Group as a local variable that is not dependent on whether actual TIP Group resource exists or not.

Let’s run terraform apply once more to marvel at our creation, and wrap up with that for now.

To sum it up, in this part we talked about:

  • Handling conditional logic in our templates,
  • How to use that logic to affect creation of resources (through count), and
  • How to use it to manipulate parameters for resources that depend on such conditions.

See you in the final Part 4!

About Dmitri Kalintsev

Some dude with a blog and opinions ;) View all posts by Dmitri Kalintsev

2 responses to “Let’s Terraform the vTM: Part 3 / 4

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 )

Facebook photo

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

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: