Your containerized application with IAC on AWS — Pt.3
Published: 2024-05-23 13:24:24

Hi Folks! This will be the final post in our series on infrastructure and containers. We will utilize Terragrunt and our infrastructure in this section, and at the conclusion, we will have our application operating on Fargate on AWS.

The docker image I’ll be using in this lesson comes from Sonic, an old game that many people associate with their early years. [You may use this image or find it on my dockerhub, whichever you would like.](https://hub.docker.com/r/shescloud/sonic-the-hedgehog)

## **DIRECTORIES**

Again, I’ll leave our directory structure here so you can guide yourself:

```
app
modules
    ├── amazon_vpc
    ├── aws_loadbalancer
    ├── aws_fargate
    ├── aws_roles
    ├── aws_ecs_cluster
    └── aws_targetgroup
    └── aws_certificate_manager

terragrunt
    └── dev
        └── us-east-1
            ├── aws_ecs
            │   ├── cluster
            │   └── service
            ├── aws_loadbalancer
            ├── amazon_vpc
            ├── aws_targetgroup
            ├── aws_roles
            ├── aws_certificate_manager
            └── terragrunt.hcl
```

## **TERRAGRUNT**
First, let’s look at our terragrunt.hcl, located in us-east-1. It will be used for all common variables in our code, as well as for creating our backend settings and the lock in the dynamodb database.

Typical variables are going to be region, project_name, domain_name, env, host_headers and container_port.

terragrunt.hcl

```
remote_state {
  backend = "s3"
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite"
  }
  config = {
    bucket           = "sonic-iac-series"
    key              = "dev/${path_relative_to_include()}/terraform.tfstate"
    region           = "us-east-1"
    encrypt          = true
    dynamodb_table   = "terraform-state-lock"
  }
}

inputs = {
   region            = "us-east-1"
   project_name      = "sonic-iac"
   env               = "dev"
   domain_name       = "your domain"
   host_headers      = "sonic.your domain"
   container_port    = "8080"
   
  tags = {
     ambiente        = "dev"
     projeto         = "sonic-iac"
     plataforma      = "aws"
     gerenciado      = "terraform/terragrunt"
   }
}

generate "provider" {
    path      = "provider.tf"
    if_exists = "overwrite"
    contents = <<EOF
provider "aws" {
  profile   = "default"
  region    = "us-east-1"
}
EOF
}
```

## **VPC**
The first resource to be created will be the VPC, as it will be needed for most of our resources.

```
terragrunt
    └── dev
        └── us-east-1
             └── amazon_vpc
                 └── terragrunt.hcl
```
We will use a range of /25, starting with IP 172.35.0.221, to construct our VPC. Four subnets — two public and two private — will be created inside it.

- VPC: 172.35.0.128/25
- Public Subnet 1: 172.35.0.128/27
- Public Subnet 2: 172.35.0.160/27
- Private Subnet 1: 172.35.0.192/27
- Private Subnet 2: 172.35.0.224/27
These code files will be created within:

terragrunt.hcl

```
include {
  path = find_in_parent_folders()
}

inputs = {
    vpc_cidr_block              = "172.35.0.128/25"
    public_subnet1_cidr_block   = "172.35.0.128/27"
    public_subnet2_cidr_block   = "172.35.0.160/27"
    private_subnet1_cidr_block  = "172.35.0.192/27"
    private_subnet2_cidr_block  = "172.35.0.224/27"
    availability_zone1 = "us-east-1a"
    availability_zone2 = "us-east-1b"
}
terraform {
  source = "../../../../modules/amazon_vpc"
  extra_arguments "custom_vars" {
    commands = [
        "apply",
        "plan",
        "import",
        "push",
        "refresh"
    ]
  }
}
```

## **IAM PERMISSIONS**
The next thing to be created will be permissions for our resources.

```
terragrunt
    └── dev
        └── us-east-1
             └── aws_roles
                 └── terragrunt.hcl
```
terragrunt.hcl

```
include {
  path = find_in_parent_folders()
}

terraform {
  source = "../../../../modules/aws_roles"
  extra_arguments "custom_vars" {
    commands = [
        "apply",
        "plan",
        "import",
        "push",
        "refresh"
    ]
  }
}
```

## **AWS CERTIFICATE MANAGER**
These are the configurations for applying our certificate; we will generate the certificate and use our domain to validate it.

```
terragrunt
    └── dev
        └── us-east-1
             └── aws_certificate_manager
                 └── terragrunt.hcl
```
terragrunt.hcl

```
include {
  path = find_in_parent_folders()
}

terraform {
  source = "../../../../modules/aws_certificate_manager"
  extra_arguments "custom_vars" {
    commands = [
        "apply",
        "plan",
        "import",
        "push",
        "refresh"
    ]
  }
}
```

## **AWS LOAD BALANCER**
Let’s set up our loadbalancer using Terragrunt now. This will help distribute our traffic and guarantee that our application is highly available.

```
terragrunt
    └── dev
        └── us-east-1
             └── aws_loadbalancer
                 └── terragrunt.hcl
```
Our Terragrunt setup looks like this. It’s important to note that in order to increase everything’s dynamic nature, we use dependencies between modules.

terragrunt.hcl

```
include {
  path = find_in_parent_folders()
}

dependency "vpc" {
  config_path = "../amazon_vpc"
}

dependency "acm" {
  config_path = "../aws_certificate_manager"
}

inputs = {
  vpc_id       = dependency.vpc.outputs.vpc_id
  subnet_id_1  = dependency.vpc.outputs.public_subnet1_id
  subnet_id_2  = dependency.vpc.outputs.public_subnet2_id
  alb_internal = false
  certificate_arn = dependency.acm.outputs.acm_arn
  priority_listener_rule  = "1"
}
terraform {
  source = "../../../../modules/aws_loadbalancer"
  extra_arguments "custom_vars" {
    commands = [
      "apply",
      "plan",
      "import",
      "push",
      "refresh"
    ]
  }
}
```

## **AWS TARGET GROUP**
Here we will configure our Target Group with Terragrunt, it is super essential for directing traffic to the correct servers for our application.

```
terragrunt
    └── dev
        └── us-east-1
             └── aws_targetgroup
                 └── terragrunt.hcl
```
terragrunt.hcl

```
include {
  path = find_in_parent_folders()
}
dependency "loadbalancer" {
  config_path = "../aws_loadbalancer"
}  
  dependency "vpc" {
  config_path = "../amazon_vpc"
}

dependency "acm" {
  config_path = "../aws_certificate_manager"
}

inputs = {
  vpc_id                  = dependency.vpc.outputs.vpc_id
  subnet_id_1             = dependency.vpc.outputs.public_subnet1_id
  subnet_id_2             = dependency.vpc.outputs.public_subnet2_id
  certificate_arn         = dependency.acm.outputs.acm_arn
  listener_ssl_arn        = dependency.loadbalancer.outputs.listener_ssl_arn
  priority_listener_rule  = "2"
  health_check_path       = "/"
}

terraform {
  source = "../../../../modules/aws_targetgroup"
  extra_arguments "custom_vars" {
    commands = [
      "apply",
      "plan",
      "import",
      "push",
      "refresh"
    ]
  }
}
```

## **ECS CLUSTER**
In this step we will create our ECS cluster that will host our application.

```
terragrunt
    └── dev
        └── us-east-1
             └── aws_ecs
                 └── cluster
                       └── terragrunt.hcl
```
terragrunt.hcl

```
include {
  path = find_in_parent_folders()
}

terraform {
  source = "../../../../../modules/aws_ecs_cluster"
  extra_arguments "custom_vars" {
    commands = [
        "apply",
        "plan",
        "import",
        "push",
        "refresh"
    ]
  }
}
```

## **FARGATE AND ECR**
We will construct our fargate service, the repository in the ECR, and a record on our domain as the final configuration file.

```
terragrunt
    └── dev
        └── us-east-1
             └── aws_ecs
                 └── service
                       └── terragrunt.hcl
```
terragrunt.hcl

```
include {
  path = find_in_parent_folders()
}
dependency "loadbalancer" {
  config_path = "../../aws_loadbalancer"
}  
  dependency "vpc" {
  config_path = "../../amazon_vpc"
}
dependency "role" {
  config_path = "../../aws_roles"
}

dependency "targetgroup" {
  config_path = "../../aws_targetgroup"
}

dependency "cluster" {
  config_path = "../cluster"
}

inputs = {
  vpc_id                = dependency.vpc.outputs.vpc_id
  subnet_id_1           = dependency.vpc.outputs.private_subnet1_id
  subnet_id_2           = dependency.vpc.outputs.private_subnet2_id
  alb_dns_name          = dependency.loadbalancer.outputs.alb_dns_name
  sg_alb                = dependency.loadbalancer.outputs.alb_secgrp_id
  target_group_arn      = dependency.targetgroup.outputs.tg_alb_arn
  cluster_arn           = dependency.cluster.outputs.cluster_arn
  ecs_role_arn          = dependency.role.outputs.ecs_role_arn
  instance_count        = "1"
  container_vcpu        = "512"
  container_memory      = "1024"
  aws_account_id        = "your account number"
}

terraform {
  source = "../../../../../modules/aws_fargate"
  extra_arguments "custom_vars" {
    commands = [
        "apply",
        "plan",
        "import",
        "push",
        "refresh"
    ]
  }
}
```

---

## **APPLY**
After the entire structure has been created, you must apply terragrunt to all directories that contain terragrunt.hcl in the following order.

terragrunt/dev/us-east-1
terragrunt/dev/us-east-1/amazon_vpc
terragrunt/dev/us-east-1/aws_roles
terragrunt/dev/us-east-1/aws_certificate_manager
terragrunt/dev/us-east-1/aws_loadbalancer
terragrunt/dev/us-east-1/aws_targetgroup
terragrunt/dev/us-east-1/aws_ecs/cluster
terragrunt/dev/us-east-1/aws_ecs/fargate
Use this command on terminal to apply. You need use in each directory

`terragrunt apply
`

or in the root folder use:
`terragrunt run-all apply
`

---

## **ECR**
Now we have applied all our infrastructure and our ECR repository has been created, we must upload our image for use in our container.

The image must be downloaded from Docker Hub as an initial step. You can use another image if you prefer or your own from your application.

use this command to download my sonic image:
`docker pull shescloud/sonic-the-hedgehog
`

![ECR](https://miro.medium.com/v2/resize:fit:720/format:webp/1*X4U9B_s7J_RrfcaO4ZH3TA.gif)


![ECR](https://miro.medium.com/v2/resize:fit:720/format:webp/1*kCN5TLFuMzbYRbL70hbvDA.gif)

![ECR](https://miro.medium.com/v2/resize:fit:720/format:webp/1*jqadBO7zq6oWTH8vPKDV8g.gif)


## **TESTING**
I used a domain I had and our application was temporarily hosted at sonic.shescloud.tech.


![TESTING](https://miro.medium.com/v2/resize:fit:720/format:webp/1*7wZ9hkEGKtjTkhJJtCvx7w.gif)

**DESTROY**
If you are using it for study, or as a way to complete a test, don’t forget to destroy all resources at the end to avoid unnecessary costs. To delete everything, we will do a process similar to apply, but in the opposite way.

Before deleting everything via terragrunt, you need to access your AWS account, go to the ECR service and delete the image from the repository. After completing this step, you can proceed with destroying each of the repositories.


![Image description](https://miro.medium.com/v2/resize:fit:720/format:webp/1*8906h3PF-9BjFzg6js8tyA.gif)

Now, you must destroy to all directories that contain terragrunt.hcl in the following order.

1. terragrunt/dev/us-east-1/aws_ecs/fargate
2. terragrunt/dev/us-east-1/aws_ecs/cluster
3. terragrunt/dev/us-east-1/aws_targetgroup
4. terragrunt/dev/us-east-1/aws_loadbalancer
5. terragrunt/dev/us-east-1/aws_roles
6. terragrunt/dev/us-east-1/aws_certificate_manager
7. terragrunt/dev/us-east-1/amazon_vpc
8. terragrunt/dev/us-east-1

Use this command on terminal to destroy. You need use in each directory

`terragrunt destroy`

or in the root folder use:
`terragrunt run-all destroy
`
---

## **GITHUB**
You can check the repository with the code on my github:
https://github.com/shescloud/terraform-terragrunt-fargate

---


And that’s it folks! I hope you enjoyed it and get a lot out of this code. See u soon!