Getting Started with Terraform and AOS-CX

Terraform is a relatively new stateful open-source infrastructure as code tool created by Hashicorp. It allows users to define resources and infrastructure in declarative configuration files to build, change, and version infrastructure. Terraform is very popular in the data center environment due to it's human readable syntax and it's ability to provision across multiple cloud and on premises systems. It is considered one of the more advanced frameworks for orchestration and infrastructure.

image from HashiCorpimage from HashiCorp

image from HashiCorp

How Terraform Works

Terraform uses a high-level configuration language called HashiCorp Configuration Language (HCL) to define a desired end state in configuration files. Terraform requires two input sources:

  • Terraform Configuration: User defines what needs to be configured or provisioned and their desired state
  • Terraform State: Current state of devices/infrastructure

The Terraform configuration files use "providers" created by vendors to define resources that allow the tool to communicate and configure the infrastructure.

Terraform uses a declarative approach and determines what actions to take based on the current state devices are in compared to what is defined in the TF (Terraform) configuration files. It does this by executing the following stages:

  • Refresh: TF will query the infrastructure provider to get the current state.
  • Plan: TF will create an execution plan based on the difference between the defined configuration and the current state.
  • Apply: TF will execute the plan and save the details of the provisioned resources.
  • Destroy (optional): TF will destroy the resources/infrastructure, it works similar to "Apply" in which it will get the current state and then create/execute a plan to remove the resources.

It is recommended to follow HashiCorp's documentation that explains all the different components of Terraform and how to get started to provision your infrastructure.

Installing Terraform

❗️

Refer to Terraform Installation Guide

Please refer to the Terraform Installation Guide for up-to-date information regarding installation, or if you are having trouble installing Terraform.

Terraform & Ansible

In the industry it is common to see implementations of Terraform alongside Ansible. Ansible, an automation framework owned by RedHat, is known for it's ability to be a strong configuration tool. With the networking industry moving towards embracing practices of Infrastructure as Code, these two tools often work together in provisioning and configuring networks and infrastructure.

Terraform

Ansible

Stateful

Stateless

Keeps a state and looks to ensure the config matches the state. Is aware of all previous changes.

Push out the intent of a playbook. Ansible has no view of what has gone before.

More focused on cloud automation.

Very much focused on infrastructure automation.

Providers are written in Go

Modules are written in Python.

Terraform config file is written in HCL
Hashicorp configuration language.

Playbooks are written in YAML.

Terraform is owned and developed by Hashicorp

Ansible is owned and developed by IBM (RedHat)

Terraform does not use SSH directly and expects a plugin or provider.

Ansible uses SSH to connect to devices

table provided by Roger Perkin

Using Ansible with Terraform to configure AOS-CX

While we are planning provider development for Terraform integration, users can still utilize Terraform alongside Ansible and make use of our AOS-CX collection to provision their CX switches.

The installation of Ansible can be on the same machine as Terraform or they can be running on separate instances, the only requirement is that they're reachable from each other. First you'll need to define your Ansible playbook with the desired configurations you'd like to execute, for instructions on how to use Ansible and the AOS-CX collection see our Getting Started with Ansible and AOS-CX guide. For example playbooks see our AOS-CX Ansible Workflows repository on Github. Once your Ansible playbook is defined, it's time to write your Terraform configuration file.

Your installation of Ansible (same instance or different instance as TF) will determine how you define your Terraform configuration file. Please follow the corresponding section below to what matches your environment.

Ansible & Terraform installed on the same instance

Since we're not utilizing a specific resource but rather attempting to execute Ansible, we'll utilize Terraform's null_resource provider which implements the standard resource lifecycle but takes no further action.

We've named this resource "ansible_local" in this example but you can name it anything that fits your needs. Then we use the "local-exec" provisioner to specify a command that will be executed on the same machine that Terraform is running. We also have to define a trigger, since Terraform does a state check to determine whether a plan can be created/executed and in this instance our resource has no "state" just execution.

The entire main.tf file is as follows:

resource "null_resource" "ansible_local" {
  # Allows the playbook to always be executed, Ansible has it's own validation
  # of idempotency that we'll depend on
  triggers = {
    always_run = "${timestamp()}"
  }
  provisioner "local-exec" {
    command = "ansible-playbook playbook.yml -i hosts.yml"
  }
}

Once that's defined, you run Terraform as you typically would by executing the terraform apply command and accepting (or auto-approving) the proposed changes, your output should show the execution of the Ansible playbook similar to that below:

[email protected]:~/workspace$ terraform apply -auto-approve                          null_resource.ansible_local: Refreshing state... [id=3964945263498206328]
null_resource.ansible_remote: Refreshing state... [id=2867856337308266296]
null_resource.ansible: Refreshing state... [id=3548433629585765709]

Terraform used the selected providers to generate the
following execution plan. Resource actions are
indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # null_resource.ansible_local is tainted, so must be replaced
-/+ resource "null_resource" "ansible_local" {
      ~ id       = "3964945263498206328" -> (known after apply)
      ~ triggers = {
          - "always_run" = "2022-01-30T21:39:12Z"
        } -> (known after apply)
    }

Plan: 1 to add, 0 to change, 1 to destroy.
null_resource.ansible_local: Destroying... [id=3964945263498206328]
null_resource.ansible_local: Destruction complete after 0s
null_resource.ansible_local: Creating...
null_resource.ansible_local: Provisioning with 'local-exec'...
null_resource.ansible_local (local-exec): Executing: ["/bin/sh" "-c" "ansible-playbook playbook.yml -i hosts.yml"]

null_resource.ansible_local (local-exec): PLAY [all] *********************************************************************

null_resource.ansible_local (local-exec): TASK [Create VLAN 300 with description and name] *******************************
null_resource.ansible_local (local-exec): changed: [aoscx_1]

null_resource.ansible_local (local-exec): PLAY RECAP *********************************************************************
null_resource.ansible_local (local-exec): aoscx_1                    : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

null_resource.ansible_local: Creation complete after 5s [id=8741234644291848035]

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

Ansible & Terraform installed on different instances

Similar to the previous workflow, since we're not utilizing a specific resource but rather attempting to execute Ansible, we'll utilize Terraform's null_resource provider which implements the standard resource lifecycle but takes no further action. We've named this resource "ansible_remote" in this example but you can name it anything that fits your needs. Then we use the "remote-exec" provisioner to specify a the command that will be executed on the remote machine. Since this is a remote resource, we have to define the connection for this resource, in the below example we are using an SSH connection and hardcoding the credentials to the machine. For variable inputs or secure methods for credentials, refer to Terraform's documentation and best practices.

The entire main.tf file is as follows:

resource "null_resource" "ansible_remote" {
  # Allows the playbook to always be executed, Ansible has it's own validation
  # of idempotency that we'll depend on
  triggers = {
    always_run = "${timestamp()}"
  }

  # Establishes connection to be used by all
  # generic remote provisioners (i.e. file/remote-exec)
  connection {
    type     = "ssh"
    user     = "administrator"
    password = "password"
    host     = "10.10.12.13"
  }
  provisioner "remote-exec" {
    inline = ["ansible-playbook /home/administrator/workspace/playbook.yml -i /home/administrator/workspace/hosts.yml"]
  }
}

Once that's defined, you run Terraform as you typically would by executing the terraform apply command and accepting (or auto-approving) the proposed changes, your output should show the execution of the Ansible playbook similar to that below:

[email protected]:~/workspace$ terraform apply -auto-approve
null_resource.ansible: Refreshing state... [id=3548433629585765709]
null_resource.ansible_remote: Refreshing state... [id=8072282142250378898]
null_resource.ansible_local: Refreshing state... [id=5506317848985332448]

Terraform used the selected providers to generate the
following execution plan. Resource actions are
indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # null_resource.ansible_remote is tainted, so must be replaced
-/+ resource "null_resource" "ansible_remote" {
      ~ id       = "8072282142250378898" -> (known after apply)
      ~ triggers = {
          - "always_run" = "2022-01-30T23:09:01Z"
        } -> (known after apply)
    }

Plan: 1 to add, 0 to change, 1 to destroy.
null_resource.ansible_remote: Destroying... [id=8072282142250378898]
null_resource.ansible_remote: Destruction complete after 0s
null_resource.ansible_remote: Creating...
null_resource.ansible_remote: Provisioning with 'remote-exec'...
null_resource.ansible_remote (remote-exec): Connecting to remote host via SSH...
null_resource.ansible_remote (remote-exec):   Host: 10.80.2.153
null_resource.ansible_remote (remote-exec):   User: administrator
null_resource.ansible_remote (remote-exec):   Password: true
null_resource.ansible_remote (remote-exec):   Private key: false
null_resource.ansible_remote (remote-exec):   Certificate: false
null_resource.ansible_remote (remote-exec):   SSH Agent: false
null_resource.ansible_remote (remote-exec):   Checking Host Key: false
null_resource.ansible_remote (remote-exec):   Target Platform: unix
null_resource.ansible_remote (remote-exec): Connected!
null_resource.ansible_remote (remote-exec): [DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the
null_resource.ansible_remote (remote-exec): controller starting with Ansible 2.12. Current version: 3.6.9 (default, Dec  8
null_resource.ansible_remote (remote-exec): 2021, 21:08:43) [GCC 8.4.0]. This feature will be removed from ansible-core in
null_resource.ansible_remote (remote-exec): version 2.12. Deprecation warnings can be disabled by setting
null_resource.ansible_remote (remote-exec): deprecation_warnings=False in ansible.cfg.

null_resource.ansible_remote (remote-exec): PLAY [all] *********************************************************************

null_resource.ansible_remote (remote-exec): TASK [Gathering Facts] *********************************************************
null_resource.ansible_remote (remote-exec): ok: [aoscx_1]

null_resource.ansible_remote (remote-exec): TASK [Create VLAN 300 with description and name] *******************************
null_resource.ansible_remote (remote-exec): [WARNING]: Collection ansible.netcommon does not support Ansible version 2.11.7
null_resource.ansible_remote (remote-exec): ok: [aoscx_1]

null_resource.ansible_remote (remote-exec): PLAY RECAP *********************************************************************
null_resource.ansible_remote (remote-exec): aoscx_1                    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

null_resource.ansible_remote: Creation complete after 6s [id=2930032562156220497]

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

Did this page help you?