AWS Storage Blog

Deploying Amazon FSx for NetApp ONTAP using HashiCorp Terraform

Deploying and managing infrastructure with all the dependencies is a challenging task. Complexity easily multiplies when you add multiple environments to the equation. Manual infrastructure management leads to valuable time spent deploying the infrastructure rather than adding business value. This is assuming that the deployment is successful without any errors in the first attempt. Infrastructure-as-Code (IaC) enables you to safely and predictably create, change, and improve infrastructure.

Amazon FSx for NetApp ONTAP is ideal for users seeking a fully managed, multi-protocol shared storage solution. It frees you from intricate infrastructure management, and lets you prioritize core business tasks. To achieve consistent, error-free infrastructure deployment, consider leveraging IaC tools, which support deploying FSx for ONTAP seamlessly.

In this post, we discuss Terraform’s pros and cons and deploy an FSx for ONTAP file system using it. HashiCorp Terraform is one of the major players in the IaC market. Terraform helps managing infrastructure in hybrid and multi-cloud environments.

IaC tools

IaC is the practice of defining and provisioning infrastructure through machine-readable files, making sure of a consistent infrastructure configuration.

If you need to orchestrate multi-cloud environments, then we recommend using HashiCorp Terraform. If your entire infrastructure is on AWS, then we recommend using AWS CloudFormation. For more information on using CloudFormation to deploy FSx for ONTAP, refer to the CloudFormation and FSx User Guide. If you already have a file system deployed and are looking for a configuration management tool to maintain the configuration in a known state, then Ansible can help. You can refer to the blog How to manage Amazon FSx for NetApp ONTAP with Ansible for a brief introduction to Ansible. If you need more flexibility and want to integrate the creation of new shares with REST API, then refer to the blog How to use NetApp ONTAP REST APIs with Amazon FSx for NetApp ONTAP.

There is a wide variety of tools in the IaC, configuration management, and automation market. CloudFormation and HashiCorp Terraform are provisioning tools. Ansible is a configuration management tool. The tool you should use depends on the challenges you need to solve as well as your posture in terms of automation. For performing Day-0 activities such as provisioning we recommend using either CloudFormation or HashiCorp Terraform. If the user is entirely in AWS, then we recommend using CloudFormation because of its integration with other AWS services. If you have a need to build and manage hybrid or multi-cloud, then we recommend using HashiCorp Terraform. For performing Day-1 activities such as configuring storage, we recommend using Ansible. Ansible offers certified Modules for managing NetApp. For a complete list of NetApp Modules, refer to the collection documentation Netapp.Ontap. If you need more flexibility than what Ansible can provide, then we recommend using the REST API. In some cases, you might have to use a combination of these tools. Combinations that we recommend for managing FSx for ONTAP are provisioning and configuration management (CloudFormation/Terraform + Ansible) or provisioning and Orchestration (CloudFormation/Terraform + REST API). Refer to the additional reading section to get more insights into these tools.

Overview of components

FSx for ONTAP is a fully managed service that provides highly reliable, scalable, high-performing, and feature-rich file storage built on NetApp’s popular ONTAP file system. FSx for ONTAP provides a seamless solution for multi-protocol access, allowing the organization to focus on their core business activities instead of managing complex infrastructure. FSx for ONTAP file systems are similar to on-premises NetApp clusters. The following list identifies the main components in FSx for ONTAP:

  • File system: A file system is the primary Amazon FSx resource, analogous to an on-premises ONTAP cluster
  • Storage Virtual Machine (SVM): An SVM is an isolated file server with its own administrative credentials and endpoints for administering and accessing data
  • Volumes: ONTAP serves data to clients and hosts from logical containers called volumes

Terraform is an IaC tool that lets you build, change, and version cloud and on-premises resources safely and efficiently. Terraform can manage low-level components such as compute, storage, and networking resources, as well as high-level components such as DNS entries and Software-as-a-Service (SaaS) features. Terraform makes use of external plugins called providers to manage external resources.

Terraform provides support for provisioning across various public cloud platforms such as AWS, Azure, and Google Cloud Platform (GCP). One of its key strengths is its modularized approach, which enables the creation of reusable code in the form of Modules. This modular design enhances code organization and re-usability across projects.

Although Terraform offers powerful infrastructure provisioning capabilities, it does have some limitations. First, it doesn’t handle dependencies automatically and needs explicit definition using the Depends On property, which can be cumbersome. Additionally, Terraform stores the state of the infrastructure on the provisioning machine by default, which can be inconvenient when collaborating in a team, and necessitates the use of remote state management.

Moreover, State Files in Terraform can potentially contain sensitive information such as passwords, which demand manual security measures to protect them adequately. Another limitation is the absence of rollback support. In the event of an error during deployment, issues must be resolved manually.

The following table provides an overview of Terraform’s feature set:

Feature Terraform
Support Open-source software tool owned by HashiCorp and contributions from other companies and communities
Scope AWS, on-premises, other Cloud providers
Type of tool Orchestration tool
State management Yes (State File on local machine, remote state options such as Amazon Simple Storage Service (Amazon S3), Amazon DynamoDB, Terraform Cloud)
State File format JSON format
License and support HashiCorp offers 24×7 support plans
Change verification Yes, ‘plan’
API model Built on FSx API (except BlueXP provider)
Language Declarative language
Infrastructure Immutable*
Rollback No
External wait conditions No

Terraform is logically split into two main parts:

  1. Terraform Core
  2. Terraform plugins

Terraform Core is a binary written in Go and provides the Terraform command-line interface (CLI). A Terraform plugin is an executable binary also written in Go and exposes an implementation for a specific service, such as AWS or Azure, or a provisioner, such as bash. With FSx for ONTAP there are two providers we would use: the HashiCorp AWS provider and the NetApp Cloud Manager provider. Even though there are minor differences, both providers can create fully functional FSx for ONTAP file systems. We focus on creating the FSx for ONTAP filesystem using AWS provider in this post. Before diving deep into the procedure of creating the FSx for ONTAP file system, here are some Terraform terms that we use throughout this post:

  • Provider: A provider is a plugin that Terraform uses to create and manage your resources. You can use multiple provider blocks in your Terraform configuration to manage resources from different providers. The provider block configures the named provider, which is responsible for creating and managing resources
  • Terraform Block: The terraform {} block contains Terraform settings, such as the required providers to provision your infrastructure. Terraform installs providers from the Terraform Registry by default
  • Configuration Files: Terraform configuration files are a set of files describing infrastructure. Terraform treats all the files with the “tf” extension as a single entity and creates the resources. You can break the configuration files into multiple files or folders called Modules for convenience and reusability
  • State File: The State File contains full details of the resources deployed using the Configuration Files. Terraform compares the resource configuration in the code with the State File to determine the changes that are applied. By default, the State File is stored locally
  • Modules: Modules are a collection of standard configuration files. Modules enable you to provide reusable coding blocks that other teams can leverage
  • Resources: Resource blocks define components of your infrastructure. A resource might be a physical or virtual component, such as an Amazon Elastic Compute Cloud (Amazon EC2) instance. You can define multiple resources within a Configuration File

Prerequisites

  • The Terraform CLI (1.2.0+) installed
  • The AWS Command Line Interface (AWS CLI) installed
  • AWS account and associated credentials that allow you to create resources
  • AWS credentials are configured locally
  • VPC and Subnets we could utilize for creating the file system

Solution walkthrough

  1. Create configuration files

    Terraform needs a directory where all the configuration files are created. For this purpose, we create a directory called Terraform-FSxN and change directory to it.
    mkdir Terraform-FSxN ; cd Terraform-FSxN 
    Create the following three files and name them accordingly:

    1. touch main.tf # contains the entire configuration data of the Terraform provider, resources, etc.
    2. touch variables.tf # List the input parameters for the main.tf.
    3. touch outputs.tf # List the parameters we would like Terraform to output.
  2. Add Terraform and provider blocks

    The terraform {} block contains settings, such as the required providers Terraform uses to provision the infrastructure. The provider block configures the specified provider, and in this case aws is the required provider. aws provider’s source is defined as hashicorp/aws, which is shorthand for registry.terraform.io/hashicorp/aws.All AWS resources are named in the following format:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "4.22.0"
    }
  }
}

provider "aws" {
  # Configuration options
  profile = "default"
  region  = "us-east-1"
  default_tags {
    tags = {
      Deployment-Type = "Terraform"
      Date            = "1-Nov-2022"
    }
  }
}

  1. Add resources to the configuration file

    All the resources listed in the configuration files use the following format:

resource "resource_type" "resource_name" {
# resource-specific configuration
}

In the following code block you can observe that three resources are created. Note that the resource type always starts with {provider}_. In our case it’s going to be aws_. Resource names are specific to the configuration file and are only valid within the specific configuration file. For example terraform_fsxn or fsxn_svm are only valid within the configuration file.

# FSxN - File System
resource "aws_fsx_ontap_file_system" "terraform_fsxn" {
  storage_capacity    = var.storage_capacity
  subnet_ids          = var.subnet_ids
  deployment_type     = var.deployment_type
  preferred_subnet_id = var.preferred_subnet_id
  throughput_capacity = var.throughput_capacity
  fsx_admin_password  = random_string.fsxn_fs_admin_password.result
  kms_key_id          = var.kms_key_id
}
# FSxN - SVM
resource "aws_fsx_ontap_storage_virtual_machine" "fsxn_svm" {
  file_system_id = aws_fsx_ontap_file_system.terraform_fsxn.id
  name           = var.svmname
  active_directory_configuration {
    netbios_name = "terraform-svm"
    self_managed_active_directory_configuration {
      dns_ips                                = var.dns_ips
      domain_name                            = "CORP.EXAMPLE.COM"
      organizational_unit_distinguished_name = var.OrganizationalUnitDistinguishedName
      username                               = local.db_creds.admin
      password                               = local.db_creds.password
    }
  }
}

# FSxN - Volume 
resource "aws_fsx_ontap_volume" "fsxn_vol" {
  name                       = var.volname
  junction_path              = "/${var.volname}"
  security_style             = var.security_style
  size_in_megabytes          = var.size_in_megabytes
  storage_efficiency_enabled = var.storage_efficiency_enabled
  storage_virtual_machine_id = aws_fsx_ontap_storage_virtual_machine.fsxn_svm.id
  tiering_policy {
    name = var.tiering_policy_name
  }
}

Once you add resources to the main configuration file, it would appear like this:

## Title: main.tf  
# Create FSxN file system, SVM, volume
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "4.22.0"
    }
  }
}

provider "aws" {
  # Configuration options
  profile = "default"
  region  = "us-east-1"
  default_tags {
    tags = {
      Deployment-Type = "Terraform"
      Date            = "1-Nov-2022"
    }
  }
}


resource "random_password" "fsxn_fs_admin_password" {
  length           = 12
  number           = true
  special          = true
  override_special = "!"
}

data "aws_secretsmanager_secret_version" "creds" {
  # Fill in the name you gave to your secret
  secret_id = "corpdomainadmin"
}

locals {
  db_creds = jsondecode(
    data.aws_secretsmanager_secret_version.creds.secret_string
  )
}

# FSxN - File System

resource "aws_fsx_ontap_file_system" "terraform_fsxn" {
  storage_capacity    = var.storage_capacity
  subnet_ids          = var.subnet_ids
  deployment_type     = var.deployment_type
  preferred_subnet_id = var.preferred_subnet_id
  throughput_capacity = var.throughput_capacity
  fsx_admin_password  = random_string.fsxn_fs_admin_password.result
  kms_key_id          = var.kms_key_id
}

# FSxN - SVM

resource "aws_fsx_ontap_storage_virtual_machine" "fsxn_svm" {
  file_system_id = aws_fsx_ontap_file_system.terraform_fsxn.id
  name           = var.svmname
  active_directory_configuration {
    netbios_name = "terraform-svm"
    self_managed_active_directory_configuration {
      dns_ips                                = var.dns_ips
      domain_name                            = "CORP.EXAMPLE.COM"
      organizational_unit_distinguished_name = var.OrganizationalUnitDistinguishedName
      username                               = local.db_creds.admin
      password                               = local.db_creds.password
    }
  }
}

# FSX for ONTAP - volume

resource "aws_fsx_ontap_volume" "fsxn_vol" {
  name                       = var.volname
  junction_path              = "/${var.volname}"
  security_style             = var.security_style
  size_in_megabytes          = var.size_in_megabytes
  storage_efficiency_enabled = var.storage_efficiency_enabled
  storage_virtual_machine_id = aws_fsx_ontap_storage_virtual_machine.fsxn_svm.id

  tiering_policy {
    name = var.tiering_policy_name
  }
}

 

  1. Add variables to the second configuration file created

    All the variables used in the resource blocks are provided by the variables.tf file created. Add the following lines to it:

## Title: variables.tf
# FSX for ONTAP - File System Variables

variable "FSxNName" {
  description = "fsxn filesystem name"
  type        = string
  default     = "BlogFSxN"
}

variable "deployment_type" {
  description = "filesystem deployment type (SINGLE_AZ_1 or MULTI_AZ_1)"
  type        = string
  default     = "MULTI_AZ_1"
}

variable "subnet_ids" {
  description = "list of subnet IDs where vpc endpoints will be placed (1 or 2 list items depending on deployment_type var)"
  type        = list(any)
  default     = ["subnet-65523503", "subnet-8c9ec782"]
}

variable "preferred_subnet_id" {
  description = "subenet were primary instance will reside"
  type        = string
  default     = "subnet-65523503"
}

variable "route_table_ids" {
  description = "**ONLY APPLIES TO MULTI AZ DEPLOYMENTS** the VPC route tables in which the file system's endpoints will be created - defaults to default route table when null"
  type        = list(any)
  default     = null
}

variable "security_group_ids" {
  description = "A list of IDs for the security groups that apply to the specified network interfaces created for file system access. These security groups will apply to all network interfaces. Defaults to default security group when null"
  type        = list(any)
  default     = null
}

variable "storage_capacity" {
  description = "storage capacity in GiB of the file system. Valid values between 1024 and 196608"
  type        = number
  default     = 1024
}

variable "throughput_capacity" {
  description = "Sets the throughput capacity (in MBps) for the file system that you're creating. Valid values are 128, 256, 512, 1024, 2048 and 4096"
  type        = number
  default     = 128
}

variable "kms_key_id" {
  description = "ARN for the KMS Key to encrypt the file system at rest - if null defaults to an AWS managed KMS Key."
  type        = string
  default     = null
}

# FSxN - SVM Variables

variable "svmname" {
  description = "The name of the SVM. You can use a maximum of 47 alphanumeric characters, plus the underscore (_) special character."
  type        = string
  default     = "blog-svm"
}

variable "netbios_name" {
  type    = string
  default = "blog-svm"
}

variable "dns_ips" {
  type    = list(string)
  default = ["172.31.76.144", "172.31.8.8"]
}

variable "domain_name" {
  description = "Domain Name of the ActiveDirectoryConfiguration"
  default     = "corp.example.com"
}

variable "OrganizationalUnitDistinguishedName" {
  description = "Organization unit for FSxN"
  default     = "OU=computers,OU=corp,DC=corp,DC=example,DC=com"

}

variable "RootVolumeSecurityStyle" {
  description = "Security Style of the Root volume"
  default     = "Unix"
}

# FSxN - Volume variables
variable "volname" {
  description = "The name of the Volume. You can use a maximum of 203 alphanumeric characters, plus the underscore (_) special character"
  type        = string
  default     = "volume1"
}

variable "junction_path" {
  description = "Specifies the location in the storage virtual machine's namespace where the volume is mounted. The junction_path must have a leading forward slash, such as /vol3"
  type        = string
  default     = "/volname"
}

variable "security_style" {
  description = "Specifies the volume security style, Valid values are UNIX, NTFS, and MIXED"
  type        = string
  default     = "UNIX"
}

variable "size_in_megabytes" {
  description = "Specifies the size of the volume, in megabytes (MB), that you are creating."
  type        = string
  default     = "10240"
}

variable "storage_efficiency_enabled" {
  description = "Set to true to enable deduplication, compression, and compaction storage efficiency features on the volume."
  type        = string
  default     = "true"
}

variable "tiering_policy_name" {
  description = "Specifies the tiering policy for the ONTAP volume for moving data to the capacity pool storage. Valid values are SNAPSHOT_ONLY, AUTO, ALL, NONE"
  type        = string
  default     = "AUTO"
}
  1. Add output variables to the 3rd and the final configuration file

    Terraform can list any FSx for ONTAP attributes as the output. In our example we add the following lines to the outputs.tf file.

# FSxN Output Values

output "fsxn_arn" {
  value = aws_fsx_ontap_file_system.terraform_fsxn.id
}

output "fsxn_dns_name" {
  value = aws_fsx_ontap_file_system.terraform_fsxn.dns_name
}

output "fsxn_endpoints" {
  value = aws_fsx_ontap_file_system.terraform_fsxn.endpoints
}

output "svm_id" {
  value = aws_fsx_ontap_storage_virtual_machine.fsxn_svm.id
}
  1. Initialize the configuration directory

    Once all the configuration files are created, initiate the directory using Terraform init. Initialize a configuration directory download and install the providers used in the configuration. You need to perform this action whenever you make changes to the provider or resource configuration.

terraform init

After initializing, the working directory should look similar to the following Figure 1:

working directory

Figure 1 – Working directory

The terraform fmt command automatically updates configurations in the current directory for easy readability and consistency. The Terraform validate command makes sure that the configuration is syntactically valid and internally consistent.

terraform fmt
terraform validate

7. Preview the implementation:

You can use Terraform plan command to preview the Terraform actions. It lists all the resources that are created, deleted, and modified when you implement the configuration files.

terraform plan

Note that in this example, the State File (terraform.tfstate) that tracks infrastructure status is stored locally on the machine. For team collaboration and enhanced security, we recommend storing the State File in a remote location, such as Amazon S3 or Terraform cloud. This lets multiple people collaborate on the code. You can do it using the backend {} block within the Terraform block. Refer to the documentation on the Terraform Remote State for more information.

8. Execute the Terraform Configuration Files:

terraform apply.

As Terraform is a partner tool, there might be a delay in supporting new AWS services or features. There is no native means to extend the functionality other than the operations provided by the AWS provider. However, you can leverage aws_lambda_function and aws_lambda_function_invocation to create the resource. You can also use CloudFormation and its support for Custom resources to extend the functionality. You have to create a CloudFormation template to create resources not supported by Terraform. Furthermore, you need to add the aws_cloudformation_stack resource to the tf file calling the CloudFormation stack created earlier.

Cleaning up

To keep charges to a minimum, delete resources that you no longer need. Clean up the resources in the following sequence:

  1. Delete any non-root volumes or SVMs deployed through the console for testing.
  2. Perform aterraform destoryto delete the resources created using Terraform.

Conclusion

In this post, we provided an overview of HashiCorp Terraform and covered how to create an FSx for ONTAP file system, SVM, and a volume using Terraform. Users can automate and orchestrate FSx for ONTAP based on their needs. FSx for ONTAP supports other automation tools to add to the functionality of Terraform. Refer to the additional reading section for more information.

Thanks for reading this post! If you have any comments or questions, don’t hesitate to leave them in the comments section.

Additional reading

Deploying Amazon FSx for NetApp ONTAP using AWS CloudFormation

How to manage Amazon FSx for NetApp ONTAP with Ansible

How to use NetApp ONTAP REST APIs with Amazon FSx for NetApp ONTAP

Madhu Vinod Diwakar

Madhu Vinod Diwakar

Madhu is a Cloud Infrastructure Architect at Amazon Web Services (AWS), focusing on storage migration, performance, and optimization for customer workloads. Outside of work, Madhu likes to spend time playing racket sports like table tennis or badminton.

Sandeep Vadapalli

Sandeep Vadapalli

Sandeep Vadapalli is a Cloud Infrastructure Architect at Amazon Web Services Professional Services. In this role, Sandeep collaborates directly with clients to facilitate and expedite their migration to cloud-based solutions through the processes of building, designing, and architecting cloud-based solutions. Outside of work, Sandeep finds enjoyment in hiking and spending time in nature.