• Launching CloudEOS in AWS with Terraform

 
 
Print Friendly, PDF & Email

Launching CloudEOS in AWS with Terraform

Introduction

Enterprise cloud organizations are orchestrating environments in the cloud.  This can be done with cloud native tools such as AWS CloudFormation or Azure Resource Manager Templates.  However, Terraform is winning enterprise mindshare as a cross-cloud orchestration system, and this post is an example of a simple CloudEOS deployment into AWS using Terraform.

Diagram

Below is the diagram that will be referenced in this post.

Prerequisites

It will be assumed that the reader has familiarity with Terraform and how to setup the Terraform environment.  For basic instructions on installing and setting up a Terraform environment, see https://www.terraform.io to get started. 

Also, the AWS CLI will need to be installed on your workstation to be able to create this example.  More can be found on AWS’s site with regard to the CLI installation at https://aws.amazon.com/cli/  Enterprises will have different ways they login to the CLI, so check with your company if you are using a corporate AWS account on the recommended way to use the AWS CLI.

Terraform uses resources to define item creations, and the resources are contained in something called a provider within Terraform.  The AWS provider is what will be referenced below.  For more information on the AWS provider, reference this url: https://registry.terraform.io/providers/hashicorp/aws/latest/docs

The Terraform script in this article will be fairly simple and will encompass a main.tf file and a bootstrap file for configuring the CloudEOS instance on startup.  To begin, create a folder that you will use for the Terraform project, and create a name and the ‘.tf’ extension.  For this example, the file will be main.tf.

Provider Definition

Open the main.tf file, and begin by defining the Terraform AWS provider. In this provider, the “us-west-2” region will be used for the project.  When running Terraform, the provider will be retrieved when the initialization process starts.

provider "aws" {
   profile = "default"
   region  = "us-west-2"
}

VPC Definition

Next, define the AWS VPC that will be used as well as the cidr block of addresses.  For this example, 10.1.0.0/16 will be used as the main block of addresses for the VPC.  A Name can also be given to the resource in the form of a tag for making it easier to locate from within the AWS console.

A quick note about resources in Terraform:  A resource is defined with the ‘resource’ key word, then the resource name ‘aws_vpc’ in this case, then the name to reference the resource in the current file which is ‘VPC’ for the below example.

resource "aws_vpc" "VPC" {
   cidr_block = "10.1.0.0/16"

   tags = {
       "Name" = "Terraform-VPC"
   }
}

Subnet Definition

Now define the two subnets for the VPC to be used for the public (10.1.0.0/24) and private (10.1.1.0/24) side of the CloudEOS instance.  This is where you can divide the larger cidr block into more manageable subnets.  In the example below, PublicSubnet1 is used for the public facing side of the CloudEOS instance.  PrivateSubnet1 is used for the host facing side of CloudEOS and will not be able to communicate outside the VPC except through CloudEOS’s private interface that will be defined below.

A quick note about referencing resources in Terraform:  In the example below, the vpc_id is needed to correlate the VPC created with the subnet that is being defined.  To reference the above VPC resource, simply follow this format – [resource name].[name of resource you assigned].id

resource "aws_subnet" "PublicSubnet1" {
   vpc_id     = aws_vpc.VPC.id
   cidr_block = "10.1.0.0/24"
   availability_zone = "us-west-2a"

   tags = {
       "Name" = "PublicSubnet1"
   }
}

resource "aws_subnet" "PrivateSubnet1" {
   vpc_id     = aws_vpc.VPC.id
   cidr_block = "10.1.1.0/24"
   availability_zone = "us-west-2a"

   tags = {
       "Name" = "PrivateSubnet1"
   }
}

Internet Gateway

In AWS, an Internet Gateway needs to be defined for allowing connections in and out of the VPC for Internet connections.  The below resource defines the Internet Gateway.  

resource "aws_internet_gateway" "InternetGateway" {
   vpc_id = aws_vpc.VPC.id

   tags = {
       "Name" = "Terraform-IG"
   }
}

Security Group Definition

Security groups in AWS are like firewall rules that maintain the state of the traffic flowing through them.  Two security groups will be created for this topology.  One will be open for the communication between the CloudEOS instance and the host, and the other will allow only SSH and ICMP traffic inbound from the Internet.  The definition of each of the resources is defined below.

resource "aws_security_group" "OpenSG" {
   vpc_id = aws_vpc.VPC.id

   ingress {
       description = "Allow Any"
       from_port = 0
       to_port   = 0
       protocol  = -1
       cidr_blocks = ["0.0.0.0/0"]
   }

   egress {
       description = "Allow Any"
       from_port = 0
       to_port   = 0
       protocol  = -1
       cidr_blocks = ["0.0.0.0/0"]
   }
}

resource "aws_security_group" "InternetSG" {
   vpc_id = aws_vpc.VPC.id

   ingress {
       description = "Allow SSH"
       from_port = 22
       to_port   = 22
       protocol  = "tcp"
       cidr_blocks = ["0.0.0.0/0"]
   }

   ingress {
       description = "Allow ICMP"
       from_port = -1
       to_port = -1
       protocol = "icmp"
       cidr_blocks = ["0.0.0.0/0"]
   }

   egress {
       description = "Allow Any"
       from_port = 0
       to_port   = 0
       protocol  = -1
       cidr_blocks = ["0.0.0.0/0"]
   }
}

Network Interface Definition

For the CloudEOS instance that will be created, both the public and private interfaces need to be defined for the instance.  For the public interface in this example, it needs to be correlated to the PublicSubnet1 defined above.  A private IP is optional for the interface but helpful when troubleshooting as this is the IP that will be assigned to the interface.  The security group id that is associated to this interface needs to also be set, and since the public interface connects to the Internet, this security group will use the InternetSG security group.

The private interface will have similar information but also will include something called a source/destination check.  In AWS, each EC2 instance performs source/destination checks by default. This means that the instance must be the source or destination of any traffic it sends or receives.  Since there will be a host behind the CloudEOS private interface, the source_dest_check will be disabled, or false in Terraform, for the private interface.  The private interface will be associated to the PrivateSubnet1 defined above and will be associated to the OpenSG security group since all communication between the host and CloudEOS instance is permitted.

resource "aws_network_interface" "public" {
   subnet_id       = aws_subnet.PublicSubnet1.id
   private_ips     = ["10.1.0.10"]
   security_groups = [aws_security_group.InternetSG.id]
}
resource "aws_network_interface" "private" {
   subnet_id       = aws_subnet.PrivateSubnet1.id
   private_ips     = ["10.1.1.10"]
   source_dest_check = false
   security_groups = [aws_security_group.OpenSG.id]
}

Route Table and Routes

Route tables and routes will need to be defined for both the public and private subnets, routes will need to be created, and the route tables will need to be associated with the appropriate subnets.

Define the Route Table

The two resource definitions below define the public and private route tables to be created.

resource "aws_route_table" "PublicRouteTable" {
   vpc_id = aws_vpc.VPC.id

   tags = {
       "Name" = "PublicRT"
   }
}

resource "aws_route_table" "PrivateRouteTable" {
   vpc_id = aws_vpc.VPC.id

   tags = {
       "Name" = "PrivateRT"
   }
}

Define the Routes

Routes will need to be defined in the route tables specifying where the default traffic will be sent.  For the public route table, the default route (0.0.0.0/0) will be sent to the Internet Gateway.  For the private route table, the default route will be sent to the private network interface of the CloudEOS instance as defined above.

resource "aws_route" "PublicDefault" {
   route_table_id = aws_route_table.PublicRouteTable.id
   destination_cidr_block = "0.0.0.0/0"
   gateway_id = aws_internet_gateway.InternetGateway.id
}

resource "aws_route" "PrivateDefault" {
   route_table_id = aws_route_table.PrivateRouteTable.id
   destination_cidr_block = "0.0.0.0/0"
   network_interface_id = aws_network_interface.private.id
}

Associate Subnets to the Route Tables

Once the route tables and routes are created, AWS needs to associate the route tables to the correct subnets.  For the public route table, it will be associated to the PublicSubnet1 subnet defined above.  For the private route table, it will be associated to the PrivateSubnet1 subnet.

resource "aws_route_table_association" "route_assoc_public" {
 route_table_id = aws_route_table.PublicRouteTable.id
 subnet_id      = aws_subnet.PublicSubnet1.id
}

resource "aws_route_table_association" "route_assoc_private" {
 route_table_id = aws_route_table.PrivateRouteTable.id
 subnet_id      = aws_subnet.PrivateSubnet1.id
}

EIP

The Elastic IP is an AWS resource that allows a public IP to be held and not changed regardless of whether an instance goes offline or not.  Ephemeral public IP addresses change when an instance reboots, but the Elastic IP does not.  The Terraform resource below defines a public IP address, ties it to the network interface where it will be used, and associates it with the private IP address on that network interface.

The ‘depends_on’ attribute is used here to alert Terraform not to create the EIP before the Internet Gateway and public network interface are created.

resource "aws_eip" "PublicIP" {
   network_interface = aws_network_interface.public.id
   vpc = true
   associate_with_private_ip = "10.1.0.10"
   depends_on = [aws_internet_gateway.InternetGateway, aws_network_interface.public]
}

CloudEOS Definition

This section will define the CloudEOS instance parameters and the bootstrap image.

CloudEOS Instance

The CloudEOS instance is ready to be defined.  The resource ‘aws_instance’ defines the CloudEOS1 instance and has several parameters that need to be present.

The ‘ami’ is the image in AWS that will be used to create the CloudEOS instance.  It is available through the market place on AWS.

The ‘instance_type’ is needed to size the deployment of this instance.

The availability_zone should be consistent with the zone used in the rest of the configuration which is us-west-2a for this example.

An SSH ‘key_name’ is recommended that contains the credentials for the user to gain access to the instance if a bootstrap has not been put on the instance.  If you are using an SSH key to get to your instance it is best to have already defined that key within AWS and reference the key name in the ‘aws_instance’ below.  However, a key can also be created in AWS from the AWS CLI using the the following commands from a workstation terminal:

'aws ec2 create-key-pair --key-name test-west-2 --query 'KeyMaterial' --output text > test-west-2.pem'
'chmod 400 test-west-2.pem'

These commands will create a key in AWS and also place the key in the current directory on the workstation. The ‘chmod’ command is used on a Mac/Linux platform to change permissions for the key to be used.  Note that it is not necessary to use an SSH key in this example because the bootstrap will provide us with the username/password from EOS to login to the instance.  For more information on AWS key pairs, reference AWS documentation:  https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#prepare-key-pair

The ‘network_interface’ section defines the public and private interface on the instance which were defined above.

The ‘user_data’ section is where the base configuration of the device is defined and added upon the booting of the device.  A sample bootstrap configuration is below.  NOTE: The bootstrap.cfg file contains the familiar router configuration seen in the section below.

resource "aws_instance" "CloudEOS1" {
   ami = "ami-01bda3983d6485129"
   instance_type = "c5.xlarge"
   availability_zone = "us-west-2a"
   key_name = "test-west-2"
   network_interface {
       device_index = 0
       network_interface_id = aws_network_interface.public.id
   }

   network_interface {
       device_index = 1
       network_interface_id = aws_network_interface.private.id
   }  

   user_data = file("bootstrap.cfg")

   tags = {
       "Name" = "CloudEOS1"
   }
}

Sample Bootstrap Configuration for the CloudEOS Instance

Below is a sample bootstrap that could be loaded into the CloudEOS instance on boot.  For this example, this configuration would be located in a bootstrap.cfg text file as referenced above.

%EOS-STARTUP-CONFIG-START%
hostname CloudEOSTest
username testuser privilege 15 secret arista123
aaa authorization exec default local
ip routing
ip route 0.0.0.0/0 10.1.0.1
!
ip nat profile snat-for-leaf-routers
   ip nat source dynamic access-list acl-snat-for-leaves overload
!
ip access-list acl-snat-for-leaves
   10 permit ip any any
!
interface ethernet 1
   ip address 10.1.0.10/24
   ip nat service-profile snat-for-leaf-routers
!
interface ethernet 2
   ip address 10.1.1.10/24
!
%EOS-STARTUP-CONFIG-END%

Host

The host in this example is another CloudEOS instance that just has one default interface defined on it with the OpenSG security group assigned to that interface.  The bootstraphost.cfg is similar to the one above and is provided as an example below.

resource "aws_instance" "CloudEOSHost" {
   ami = "ami-01bda3983d6485129"
   instance_type = "c5.xlarge"
   availability_zone = "us-west-2a"
   key_name = "test-west-2"
   subnet_id = aws_subnet.PrivateSubnet1.id
   vpc_security_group_ids = [aws_security_group.OpenSG.id]
   user_data = file("bootstraphost.cfg")
   tags = {
       "Name" = "CloudEOS-Host"
   }
}

Sample Bootstrap Configuration for the Host

Below is a sample bootstrap that is used for the host in this example.  A standard compute resource could be used instead as desired.  For this example, this configuration would be located in a bootstraphost.cfg text file as referenced above.  Note that ‘interface ethernet 1’ is missing from this configuration as it will get its address from DHCP, so you would need to get the address from the AWS console or use output from Terraform which is included in the outputs below.

%EOS-STARTUP-CONFIG-START%
hostname Host
username testuser privilege 15 secret arista123
ip routing
ip route 0.0.0.0/0 10.1.1.10
!
%EOS-STARTUP-CONFIG-END%

Output

In Terraform, an output can be defined to provide the user with information about a created resource.  In this case, the output provides the public IP address of the CloudEOS instance to allow the user to SSH into the device after creation.

output "public-ip" {
value = aws_eip.PublicIP.public_ip
}

output "host-ip" {
value = aws_instance.CloudEOSHost.private_ip
}

Running the Terraform Script

Now that the script is complete, follow the following steps to create the instances in AWS

  1. Login to the AWS CLI from a terminal window.
  2. From the directory where the main.tf, bootstrap.cfg, and bootstraphost.cfg files are, run ‘terraform init
  3. Next, run ‘terraform plan’ to make sure you do not have any errors.
  4. Run ‘terraform apply’ to begin the process of deploying your topology.  You will be asked to type ‘yes’ for your confirmation to build the resources in AWS.  Once this is complete, Terraform will provide the IP address from the output for you to connect to SSH to the CloudEOS instance in AWS.  Use the username/password defined in the bootstrap files for the credentials to login to the instance, or use your SSH key if you prefer.
  5. Run ‘terraform destroy’ to tear down the topology in AWS.  You will be asked to type ‘yes’ for your confirmation to remove the resources in AWS.

Additional Arista Terraform Example Material

When you are ready to learn about how to create multi-region solutions, enable segmentation from the data center to the cloud, incorporate CloudHA, and tie everything back into CloudVision as a Service, all using Terraform and EOS, visit Arista’s Github page at https://github.com/aristanetworks/CloudEOS

Follow

Get every new post on this blog delivered to your Inbox.

Join other followers: