DEV Community

Marko Milosavljevic
Marko Milosavljevic

Posted on

Terraform Modules and Workspaces: Techniques for Modular and Scalable Infrastructure in AWS

Managing infrastructure is a challenging task when dealing with complex infrastructures and multiple teams. Luckily Terraform as provisioning tool is capable of simplifying this process by using modules and workspaces. This allows you to build modular, reusable and scalable infrastructure.
In this post, we will dive into some techniques for creating and managing a Terraform modules and workspaces by covering:

  1. Creating reusable modules
  2. Managing module dependencies
  3. Utilizing Terraform workspaces
  4. Separate variables for workspaces and environments

1.Creating reusable modules
A Terraform module is reusable block and you can think of it as a Python method which has some logic and expects you to pass some parameters when calling it. Module is self-contained configuration that groups related resources into a logical unit. Modules are being used mostly to reduce complexity and repetition.
Terraform module shines for code organization in terms of breaking down complex infrastructure into smaller chunks that have meaning. Additionally it improves consistency by applying the exact same configuration across environments to ensure standardization.

Terraform module example:
Example of creating module for AWS S3 related resources and how to parametrise the module for reuse across project:

resource "aws_s3_bucket" "example" {
  bucket = var.bucket_name
}

resource "aws_s3_bucket_public_access_block" "example" {
  bucket = aws_s3_bucket.example.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_versioning" "example" {
  bucket = aws_s3_bucket.example.id
  versioning_configuration {
    status = var.s3_versioning
  }
}

resource "aws_s3_bucket_policy" "example" {
  bucket = aws_s3_bucket.example.id
  policy = var.s3_bucket_policy
}

variable "bucket_name" {
  description = "The name of the bucket"
  type        = string
}

variable "s3_bucket_policy" {
  description = "Value of the s3 bucket policy"
  type        = string
  default     = ""
}

variable "s3_versioning" {
  description = "Versioning status for S3 bucket"
  type        = string
  default     = "Enabled"
}

Enter fullscreen mode Exit fullscreen mode

By parameterizing bucket name, versioning and policy, this S3 module can be reused for multiple purposes and environments. When calling S3 module, you need to pass the variables each time.

module "test_s3_bucket" {
  source = "../modules/s3"
  bucket_name             = "test-s3-bucket"
  s3_bucket_policy        = data.aws_iam_policy_document.test_s3_bucket_access.json
}
Enter fullscreen mode Exit fullscreen mode

Note that we did not pass s3_versioning to the module call, as this variable already has default value, which means each module call that does not have s3_versioning will inherit its default value. By creating a Terraform module for an S3 bucket, you grouped logically related resources, and then you can reuse and update it further with some new S3 configurations or resources, depending on the needs.

2. Managing module dependencies
In complex projects, you will most likely have a lot of modules, and occasionally those will depend on one another. Managing dependencies and versioning between modules ensures you deploy stable configurations while allowing updates when needed.

Handling Module Dependencies
Terraform manages dependencies based on the references between resources, meaning if you pass output from module A as input when calling module B, Terraform will automatically orders them. Here is a simple example:

resource "aws_iam_policy" "lambda_policy" {
  name        = "LambdaPermissionsPolicy"
  description = "IAM policy for S3 access"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "s3:PutObject",
          "s3:GetObject",
          "s3:ListBucket"
        ]
        Resource = [
          module.s3_bucket.bucket_arn
        ]
      }
    ]
  })
}

#output defined in S3 module
output "bucket_arn" {
  description = "The ARN of the bucket"
  value       = aws_s3_bucket.example.arn
}
Enter fullscreen mode Exit fullscreen mode

In this example you can see IAM policy used to allow Lambda to interact with our S3 bucket. As you can see in Resource section policy is refers to the output from the S3 module which means that Terraform will first create resources from the S3 module and then the IAM policy itself.
Side note: this is called implicit dependency, which means that Terraform will discover it on its own by analysing resource attributes.

3. Utilising Terraform workspaces
Workspace is very powerful feature used for managing different parts of you project by allowing you to have multiple state files within same Terraform configuration. Imagine that your infrastructure has three major parts, one for AWS EKS related resources, one for AWS VPC and other networking components and last one for monitoring.
With this approach you will have full state isolation meaning all resources are deployed in same AWS account but with separated S3 state files. Having workspaces allows multiple teams to work on different parts of infrastructure at the same time, so if there is a need to update Grafana configuration within monitoring Terraform workspace that can be implemented and tested while some other team is making changes to EKS workspace without conflicts. This isolation shines when there is issue in lets say monitoring workspace but EKS is still operating correctly.
Side note: when using single S3 bucket for storing sate files from multiples workspaces it is good practice to use different path so that files are organised within bucket. Additionally always have replica of this bucket.

You can create a workspace using this command:

terraform workspace create monitoring
Enter fullscreen mode Exit fullscreen mode

You can switch between workspaces using:

terraform workspace select monitoring
Enter fullscreen mode Exit fullscreen mode

*4. Separate variables for workspaces and environments
*

When deploying resources to dev environment you will probably use smaller EKS nodes and for production the bigger ones. To achieve that you can use tfvars file and then for each specific environment pass, meaning you would have:

eks/dev.tfvars
eks/qa.tfvars
eks/prod.tfvars
Enter fullscreen mode Exit fullscreen mode

and of course same for other workspaces. This way each workspace can have own set of variable values per environment which means more flexibility when deploying resources.

Conclusion
Terraform's modules and workspaces are powerful features for managing scalable infrastructure. By creating reusable and parameterised modules, managing dependencies between them and utilising workspaces, you can streamline you complex infrastructure. Using tfvars files in combination with workspaces is best way to manage infrastructure dynamically based on different scenarios.

Top comments (0)