DEV Community

Cover image for Setup basic SaaS infrastructure using Terraform
jumpingcats
jumpingcats

Posted on • Updated on

Setup basic SaaS infrastructure using Terraform

Introduction

In this tutorial, we will be using Terraform to setup the infrastructure for a SaaS company from scratch. We will be using various AWS resources including S3 buckets, ACM Certificate, Route53 entries, VPC, EC2 instances, CloudWatch, Lambdas, RDS, and API Gateways.

Prerequisites

  • AWS Account
  • Terraform installed on your system
  • AWS CLI configured on your system

Getting Started

Before we start, let's create a new Terraform project and initialize it. Create a new directory and change your current directory to it.

mkdir my-saas-company
cd my-saas-company
Enter fullscreen mode Exit fullscreen mode

Now, run the following command to initialize the Terraform project.

terraform init
This will download the required plugins for the AWS provider.
Enter fullscreen mode Exit fullscreen mode

Setup S3 Buckets

We will start by setting up four S3 buckets for hosting our SaaS company's website and other data. Add the following code to a file named s3.tf.

resource "aws_s3_bucket" "hosting" {
  count          = 4
  bucket         = "my-saas-company-${count.index+1}"
  acl            = "public-read"
  force_destroy  = true
}
Enter fullscreen mode Exit fullscreen mode

This code will create four S3 buckets with names my-saas-company-1, my-saas-company-2, my-saas-company-3, and my-saas-company-4. The count and count.index+1 variables are used to create multiple resources with different names. The force_destroy attribute is used to ensure that the S3 bucket is deleted along with all its contents when the Terraform project is destroyed.

Setup ACM Certificate

Next, we will create an ACM (AWS Certificate Manager) certificate for our domain. Add the following code to the same s3.tf file.

resource "aws_acm_certificate" "certificate" {
  domain_name       = "my-saas-company.com"
  validation_method = "DNS"

  tags = {
    Name = "my-saas-company-certificate"
  }
}
Enter fullscreen mode Exit fullscreen mode

This code will create an ACM certificate for the domain my-saas-company.com using DNS validation. You can also add tags to the certificate for easier identification and management.

Setup Route53 Entries

Next, we will setup Route53 entries for our domain and S3 buckets. Add the following code to the s3.tf file.

resource "aws_route53_record" "website" {
  count       = 4
  zone_id     = aws_route53_zone.primary.zone_id
  name        = "my-saas-company-${count.index+1}.my-saas-company.com"
  type        = "A"
  alias {
    name                   = aws_s3_bucket.hosting.*.website_endpoint[count.index]
    zone_id                = aws_s3_bucket.hosting.*.hosted_zone_id[count.index]
    evaluate_target_health = true
   }
}

resource "aws_route53_zone" "primary" {
   name = "my-saas-company.com"
}
Enter fullscreen mode Exit fullscreen mode

This code will create four Route53 entries, one for each of our S3 buckets. The aws_route53_zone resource is used to create a new zone for our domain. The aws_route53_record resource is then used to create four A-type records for the subdomains of our S3 buckets. The alias attribute is used to link these records to our S3 buckets.

Setup VPC

Next, we will setup a VPC (Virtual Private Cloud) for our EC2 instances. Add the following code to a new file named vpc.tf.

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "my-saas-company-vpc"
  }
}
Enter fullscreen mode Exit fullscreen mode

This code will create a VPC with a CIDR block of 10.0.0.0/16. You can also add tags to the VPC for easier identification and management.

Setup EC2 Instances

Next, we will create EC2 instances for our workers and development environment. Add the following code to the vpc.tf file.

resource "aws_instance" "worker" {
   count = 2
   ami = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
   vpc_security_group_ids = [aws_security_group.instance.id]
   tags = {
      Name = "my-saas-company-worker-${count.index+1}"
   }
}

resource "aws_instance" "dev" {
   ami = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
   vpc_security_group_ids = [aws_security_group.instance.id]

   tags = {
      Name = "my-saas-company-dev"
   }
}

resource "aws_security_group" "instance" {
   name = "my-saas-company-instance"
   description = "Security group for EC2 instances"
   vpc_id = aws_vpc.main.id

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

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

   tags = {
      Name = "my-saas-company-instance"
   }
}
Enter fullscreen mode Exit fullscreen mode

This code will create two worker instances and one development instance. The AMI used in this example is the latest Amazon Linux AMI. You can choose any AMI that suits your needs. The aws_security_group resource is used to create a security group for our instances that allows all traffic.

Setup CloudWatch Logging

CloudWatch is a useful tool for monitoring and logging the performance and health of your AWS resources. We will setup CloudWatch logging for our EC2 instances. Add the following code to the vpc.tf file.

resource "aws_cloudwatch_log_group" "instance" {
  name              = "my-saas-company-instance"
  retention_in_days = 14

  tags = {
    Name = "my-saas-company-instance"
  }
}

resource "aws_cloudwatch_log_stream" "instance" {
  count             = 2
  name              = "my-saas-company-worker-${count.index+1}"
  log_group_name    = aws_cloudwatch_log_group.instance.name

  tags = {
    Name = "my-saas-company-worker-${count.index+1}"
  }
}

resource "aws_cloudwatch_log_stream" "dev" {
  name              = "my-saas-company-dev"
  log_group_name    = aws_cloudwatch_log_group.instance.name

  tags = {
    Name = "my-saas-company-dev"
  }
}

resource "aws_cloudwatch_log_metric_filter" "instance" {
  count             = 2
  name              = "my-saas-company-worker-${count.index+1}"
  pattern           = "[...]"
  log_group_name    = aws_cloudwatch_log_group.instance.name
  metric_transformation {
    name              = "my-saas-company-worker-${count.index+1}"
    namespace         = "my-saas-company"
    value             = "1"
    default_value     = "0"
  }

  tags = {
    Name = "my-saas-company-worker-${count.index+1}"
  }
}

resource "aws_cloudwatch_log_metric_filter" "dev" {
  name              = "my-saas-company-dev"
  pattern           = "[...]"
  log_group_name    = aws_cloudwatch_log_group.instance.name
  metric_transformation {
    name              = "my-saas-company-dev"
    namespace         = "my-saas-company"
    value             = "1"
    default_value     = "0"
  }

  tags = {
    Name = "my-saas-company-dev"
  }
}
Enter fullscreen mode Exit fullscreen mode

This code will create a CloudWatch log group and streams for our EC2 instances. It will also create metric filters that can be used to monitor specific log events. You can specify a custom pattern for the metric filters to filter out the logs that you are interested in.

Setup Lambdas

Lambdas are a useful tool for running code without the need to provision or manage servers. We will create two Lambdas for our SaaS company. Add the following code to a new file named lambda.tf.

resource "aws_lambda_function" "function" {
  count             = 2
  filename          = "lambda_function.zip"
  function_name     = "my-saas-company-lambda-${count.index+1}"
role = aws_iam_role.lambda.arn
handler = "index.handler"
runtime = "nodejs8.10"

   tags = {
      Name = "my-saas-company-lambda-${count.index+1}"
   }
}

resource "aws_iam_role" "lambda" {
   name = "my-saas-company-lambda"

   assume_role_policy = <<EOF
      {
         "Version": "2012-10-17",
         "Statement": [
            {
               "Action": "sts:AssumeRole",
               "Principal": {
                  "Service": "lambda.amazonaws.com"
               },
               "Effect": "Allow",
               "Sid": ""
            }
         ]
      }
   EOF
}

resource "aws_iam_policy" "lambda" {
   name = "my-saas-company-lambda"

   policy = <<EOF
   {
      "Version": "2012-10-17",
      "Statement": [
         {
            "Action": [
               "logs:CreateLogGroup",
               "logs:CreateLogStream",
               "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:::*",
            "Effect": "Allow"
         }
      ]
   }
   EOF
}

resource "aws_iam_policy_attachment" "lambda" {
   name = "my-saas-company-lambda"
   roles = [aws_iam_role.lambda.name]
   policy_arn = aws_iam_policy.lambda.arn
}

Enter fullscreen mode Exit fullscreen mode

This code will create two Lambda functions and the required IAM role and policy for them. The Lambda functions can be triggered by different events such as an S3 bucket event, an API Gateway event, or a custom event.

Setup RDS

Next, we will create three RDS (Relational Database Service) instances for our SaaS company. Add the following code to a new file named rds.tf.

resource "aws_db_instance" "database" {
   count = 3
   allocated_storage = 10
   storage_type = "gp2"
   engine = "mysql"
   engine_version = "5.7"
   instance_class = "db.t2.micro"
   name = "my-saas-company-database-${count.index+1}"
   username = "my-saas-company"
   password = "my-saas-company-password"
   vpc_security_group_ids = [aws_security_group.rds.id]

   tags = {
      Name = "my-saas-company-database-${count.index+1}"
   }
}

resource "aws_security_group" "rds" {
   name = "my-saas-company-rds"
   description = "Security group for RDS instances"
   vpc_id = aws_vpc.main.id

   ingress {
      from_port = 3306
      to_port = 3306
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
   }

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

   tags = {
      Name = "my-saas-company-rds"
   }
}
Enter fullscreen mode Exit fullscreen mode

This code will create three RDS instances for the MySQL database engine. The RDS instances are secured using a security group that only allows traffic on the default MySQL port (3306) from any IP address. You can customize this security group to only allow traffic from specific IP addresses or VPCs.

Setup API Gateways

API Gateways are useful for creating and managing APIs for web and mobile applications. We will create three API Gateways for our SaaS company, one for production, one for staging, and one for development. Add the following code to a new file named api-gateway.tf.

resource "aws_api_gateway_rest_api" "prod" {
   name = "my-saas-company-prod"
   description = "API Gateway for production environment"

   tags = {
       Name = "my-saas-company-prod"
   }
}

resource "aws_api_gateway_rest_api" "staging" {
   name = "my--saas-company-staging"
   description = "API Gateway for staging environment"

   tags = {
      Name = "my-saas-company-staging"
   }
}

resource "aws_api_gateway_rest_api" "dev" {
   name = "my-saas-company-dev"
   description = "API Gateway for development environment"

   tags = {
      Name = "my-saas-company-dev"
   }
}
Enter fullscreen mode Exit fullscreen mode

This code will create three API Gateway REST APIs, one for each environment. You can then create and manage different API resources, methods, and integrations for these APIs-.

Conclusion

In this tutorial, we have seen how to use Terraform to setup the infrastructure for a SaaS company from scratch. We have created various AWS resources including S3 buckets, ACM Certificate, Route53 entries, VPC, EC2 instances, CloudWatch logging, Lambdas, RDS, and API Gateways. You can further customize this infrastructure to suit your specific needs and requirements.

Top comments (0)