Chapter 4: Building Back-End CI/CD Pipeline AWS & Terraform.

Chapter 4: Building Back-End CI/CD Pipeline AWS & Terraform.

·

6 min read

In this section, We'll setup the building blocks for the backend infrastructure for the website using AWS services and Terraform.

1: Setting Up Terraform and AWS Provider

To begin, Terraform must be installed and configured on your machine.
otherwise, follow this link ---- Install Terraform | Terraform | HashiCorp Developer

Then, create a file named main.tf and copy the following Terraform configuration into it:

This configuration sets up the required provider and specifies the AWS region for your resources.

Ok so, let's break down the Terraform code into simpler terms, explaining each part:

  1. Terraform Configuration:

    • Specifies the required Terraform version and AWS provider version.
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }

  required_version = ">= 1.2.0"
}

provider "aws" {
  1. AWS Provider Configuration:

    • Specifies the AWS region where resources will be provisioned.

      region = "eu-west-2"
      #  profile = "default"
    }
  1. IAM Role for Lambda Function:

    Defines an IAM role allowing Lambda functions to be assumed, essential for their execution.

    •       # IAM Role for Lambda function
            resource "aws_iam_role" "lambda_role" {
              name = "terraform_lambda_func_Role"
              assume_role_policy = jsonencode(
                {
                  "Version" : "2012-10-17",
                  "Statement" : [
                    {
                      "Action" : "sts:AssumeRole",
                      "Principal" : {
                        "Service" : "lambda.amazonaws.com"
                      },
                      "Effect" : "Allow",
                      "Sid" : ""
                    }
                  ]
              })
            }
      
  2. IAM Policy for Lambda Function:

    • Defines an IAM policy detailing permissions required by the Lambda function, such as accessing CloudWatch logs and DynamoDB.

    •       # IAM Policy for Lambda function
            resource "aws_iam_policy" "iam_policy_for_lambda" {
              name        = "aws_iam_policy_for_terraform_lambda_func_role"
              path        = "/"
              description = "AWS IAM Policy for managing aws lambda role"
              policy = jsonencode(
                {
                  "Version" : "2012-10-17",
                  "Statement" : [
                    {
                      "Action" : [
                        "logs:CreateLogGroup",
                        "logs:CreateLogStream",
                        "logs:PutLogEvents"
                      ],
                      "Resource" : "arn:aws:logs:*:*:*",
                      "Effect" : "Allow"
                    },
                    {
                      "Effect" : "Allow",
                      "Action" : [
                        "dynamodb:UpdateItem",
                        "dynamodb:GetItem",
                        "dynamodb:PutItem"
                      ],
                      "Resource" : "arn:aws:dynamodb:eu-west-2:662406500657:table/visitor_count_ddb"
                    },
                  ]
              })
            }
      
  3. Attachment of IAM Policy to IAM Role:

    Attaches the previously defined IAM policy to the IAM role created for Lambda functions.

    •       # Attach IAM Policy to IAM Role
            resource "aws_iam_role_policy_attachment" "attach_iam_policy_to_iam_role" {
              role       = aws_iam_role.lambda_role.name
              policy_arn = aws_iam_policy.iam_policy_for_lambda.arn
      
  4. IAM Role for Lambda Function:

    • Defines an IAM role allowing Lambda functions to be assumed, essential for their execution. While IAM Policy for Lambda Function defines an IAM policy detailing permissions required by the Lambda function, such as accessing CloudWatch logs and DynamoDB.

    •       # Archive Python code into a zip file
            data "archive_file" "zip_the_python_code" {
              type        = "zip"
              source_dir  = "${path.module}/lambda/"
              output_path = "${path.module}/lambda/lambda_function.zip"
            }
      
            # Lambda Function
            resource "aws_lambda_function" "terraform_lambda_func" {
              filename      = "${path.module}/lambda/lambda_function.zip"
              function_name = "terraform_lambda_func"
              role          = aws_iam_role.lambda_role.arn
              handler       = "lambda_function.lambda_handler"
              runtime       = "python3.8"
              depends_on    = [aws_iam_role_policy_attachment.attach_iam_policy_to_iam_role]
              environment {
                variables = {
                  databaseName = "visitor_count_ddb"
                }
              }
            }
      
  5. Archiving Python Code & Function Creation:

    • Archives Python code into a zip file, which will be uploaded to Lambda.

      Lambda Function Creation:

      • Creates a Lambda function using the archived Python code.

      • Specifies the function's role, handler, runtime, and environment variables.

    •       # Archive Python code into a zip file
            data "archive_file" "zip_the_python_code" {
              type        = "zip"
              source_dir  = "${path.module}/lambda/"
              output_path = "${path.module}/lambda/lambda_function.zip"
            }.
      
            # Lambda Function
            resource "aws_lambda_function" "terraform_lambda_func" {
              filename      = "${path.module}/lambda/lambda_function.zip"
              function_name = "terraform_lambda_func"
              role          = aws_iam_role.lambda_role.arn
              handler       = "lambda_function.lambda_handler"
              runtime       = "python3.8"
              depends_on    = [aws_iam_role_policy_attachment.attach_iam_policy_to_iam_role]
              environment {
                variables = {
                  databaseName = "visitor_count_ddb"
                }
              }
      
  6. CloudWatch Log Group & API Gateway Creation:

    • Creates a CloudWatch log group for storing logs generated by the Lambda function.

      API Gateway API Creation:

    • Creates an API Gateway API for triggering Lambda functions.

    • Specifies CORS configuration to allow cross-origin requests as discussed earlier.

    •       # CloudWatch Log Group
            resource "aws_cloudwatch_log_group" "api_gw" {
              name              = "visitor_count_log_group"
              retention_in_days = 30
            }
      
            # API Gateway API
            resource "aws_apigatewayv2_api" "lambda" {
              name          = "visitor_count_CRC"
              protocol_type = "HTTP"
              description   = "Visitor count for Cloud Resume Challenge"
              cors_configuration {
                allow_origins = ["https://devtej.com"]
              }
            }
      
  7. API Gateway Stage Creation:

    • Creates a stage for the API Gateway API, enabling deployment and access logging.

      API Gateway Integration Creation:

    • Defines an integration between the API Gateway and the Lambda function, specifying the integration type and method.

    • 
        # API Gateway Stage
        resource "aws_apigatewayv2_stage" "lambda" {
          api_id = aws_apigatewayv2_api.lambda.id
      
          name        = "default"
          auto_deploy = true
      
          access_log_settings {
            destination_arn = aws_cloudwatch_log_group.api_gw.arn
      
            format = jsonencode({
              requestId               = "$context.requestId"
              sourceIp                = "$context.identity.sourceIp"
              requestTime             = "$context.requestTime"
              protocol                = "$context.protocol"
              httpMethod              = "$context.httpMethod"
              resourcePath            = "$context.resourcePath"
              routeKey                = "$context.routeKey"
              status                  = "$context.status"
              responseLength          = "$context.responseLength"
              integrationErrorMessage = "$context.integrationErrorMessage"
            })
          }
      
          tags = {
            Name = "Cloud Resume Challenge"
          }
        }
      
        # API Gateway Integration
        resource "aws_apigatewayv2_integration" "terraform_lambda_func" {
          api_id             = aws_apigatewayv2_api.lambda.id
          integration_uri    = aws_lambda_function.terraform_lambda_func.invoke_arn
          integration_type   = "AWS_PROXY"
          integration_method = "POST"
        }
      
  8. API Gateway Route Creation:

    • Creates a route in the API Gateway, associating it with the previously defined integration.

      Lambda Permission for API Gateway:

    • Grants permission to the API Gateway to invoke the Lambda function.

    •   # API Gateway Route
        resource "aws_apigatewayv2_route" "terraform_lambda_func" {
          api_id    = aws_apigatewayv2_api.lambda.id
          route_key = "ANY /terraform_lambda_func"
          target    = "integrations/${aws_apigatewayv2_integration.terraform_lambda_func.id}"
        }
      
        # Lambda Permission for API Gateway
        resource "aws_lambda_permission" "api_gw" {
          statement_id  = "AllowExecutionFromAPIGateway"
          action        = "lambda:InvokeFunction"
          function_name = aws_lambda_function.terraform_lambda_func.function_name
          principal     = "apigateway.amazonaws.com"
          source_arn    = "${aws_apigatewayv2_api.lambda.execution_arn}/*/*"
        }
      
  9. DynamoDB Table Creation:

    Creates a DynamoDB table for storing visitor count data.

    Specifies the table's attributes, billing mode, and optional global secondary index.

    DynamoDB Table Item Creation:

    Adds an initial item to the DynamoDB table, setting the visitor count to 1.

    •   # DynamoDB Table
        resource "aws_dynamodb_table" "visitor_count_ddb" {
          name         = "visitor_count_ddb"
          billing_mode = "PAY_PER_REQUEST"
          hash_key     = "id"
      
          attribute {
            name = "id"
            type = "S"
          }
      
          attribute {
            name = "visitor_count"
            type = "N"
          }
      
          global_secondary_index {
            name            = "visitor_count_index"
            hash_key        = "visitor_count"
            projection_type = "ALL"
            read_capacity   = 10
            write_capacity  = 10
          }
      
          tags = {
            Name = "Cloud Resume Challenge"
          }
        }
      
        # DynamoDB Table Item
        resource "aws_dynamodb_table_item" "visitor_count_ddb" {
          table_name = aws_dynamodb_table.visitor_count_ddb.name
          hash_key   = aws_dynamodb_table.visitor_count_ddb.hash_key
      
          item = <<ITEM
        {
          "id": {"S": "Visits"},
          "visitor_count": {"N": "1"}
        }
        ITEM
          lifecycle {
            ignore_changes = all
          }
        }
      

      Creates a DynamoDB table for storing visitor count data.

    • Specifies the table's attributes, billing mode, and optional global secondary index.

      DynamoDB Table Item Creation:

    • Adds an initial item to the DynamoDB table, setting the visitor count to 1.

      2: Deployment

      To guarantee the effective deployment of our Terraform backend infrastructure. We'll be using the following terraform commands;
      terraform init, terraform plan, and terraform apply

      1. Terraform Init: The terraform init command ensures that your Terraform environment is properly configured and ready to use for planning, applying, and managing your infrastructure as code. It downloads and installs the necessary plugins of the provider and sets up the configuration for the backend.

      2. Terraform Plan: The terraform plan command examines the configuration files in your working directory and determines the changes that need to be made to achieve the desired state of your infrastructure. The plan gives an overview of the actions that Terraform will take to achieve the desired state, such as creating, modifying, or destroying resources. It assists in validating our configuration and comprehending the consequences of the alterations prior to implementation.

      3. Terraform Apply: The terraform apply command allows you to enact the changes defined in your Terraform configuration files, allowing you to provision and manage your infrastructure in a consistent and reproducible manner.

We can make sure that our Terraform backend infrastructure is correctly initialised, validated, and deployed by implementing these deployment processes into our workflow. By automating the deployment process, we can improve its consistency, dependability, and efficiency.