DevOps/IaC

[IaC] Terraform을 이용한 AWS 서비스 구축 (ECS, DynamoDB, VPC, Security Group 등)

ki1111m2 2023. 9. 11. 11:34

  1. 개발자가 새로운 버전을 릴리즈하면 Git Action을 통해 자동화되어 진행됩니다. ECR 이미지 Push, 새 태스크 정의 생성, ECS 서비스 업데이트가 포함됩니다.
  2. Route 53을 통해 지정한 도메인으로 사용자가 api 요청을 보냅니다.
  3. DynamoDB에 접근하여 CRUD 요청을 처리합니다.

 

전체 파일 구성은 다음과 같다.

관리를 위해 서비스 별로 파일을 나누어 구성하는 것을 선호한다.

 

main.tf

provider "aws" {
  region = "ap-northeast-2"
}

 

vpc.tf

# vpc 생성
resource "aws_vpc" "mb_vpc" {
  cidr_block = "10.0.0.0/16"
  enable_dns_hostnames = true

  tags = {
    Name = "mb_vpc"
  }
}

# 퍼블릭 서브넷 생성
resource "aws_subnet" "public_subnet_1" {
  vpc_id     = aws_vpc.mb_vpc.id
  cidr_block = "10.0.0.0/20"
  availability_zone = "ap-northeast-2a"
  map_public_ip_on_launch = true
  
  tags = {
    Name = "mb-pub-sub-1"
  }
}

resource "aws_subnet" "public_subnet_2" {
  vpc_id     = aws_vpc.mb_vpc.id
  cidr_block = "10.0.16.0/20"
  availability_zone = "ap-northeast-2c"
  map_public_ip_on_launch = true

  tags = {
    Name = "mb-pub-sub-2"
  }
}

# 프라이빗 서브넷 생성
resource "aws_subnet" "private_subnet_1" {
  vpc_id     = aws_vpc.mb_vpc.id
  cidr_block = "10.0.128.0/20"
  availability_zone = "ap-northeast-2a"

  tags = {
    Name = "mb-prv-sub-1-by-tf"
  }
}

resource "aws_subnet" "private_subnet_2" {
  vpc_id     = aws_vpc.mb_vpc.id
  cidr_block = "10.0.144.0/20"
  availability_zone = "ap-northeast-2c"

  tags = {
    Name = "mb-prv-sub-2-by-tf"
  }
}

# 서브넷 그룹 설정
resource "aws_db_subnet_group" "mb_subnet_group" {
  name = "mb_subnet_group"
  
  subnet_ids = [aws_subnet.private_subnet_1.id, aws_subnet.private_subnet_2.id]
  
}

# IGW 생성
resource "aws_internet_gateway" "mb-igw" {
  vpc_id = aws_vpc.mb_vpc.id

  tags = {
    Name = "mb-igw"
  }
}

# 라우팅 테이블 생성
resource "aws_route_table" "pubroutetable" {
  vpc_id = aws_vpc.mb_vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.mb-igw.id
  }

  tags = {
    Name = "mb-public-route-table"
  }
}

# 라우팅 테이블 연결
resource "aws_route_table_association" "pubassociation1" {
  subnet_id      = aws_subnet.public_subnet_1.id
  route_table_id = aws_route_table.pubroutetable.id
}

resource "aws_route_table_association" "pubassociation2" {
  subnet_id      = aws_subnet.public_subnet_2.id
  route_table_id = aws_route_table.pubroutetable.id
}

# NGW 생성
resource "aws_eip" "eip" {
  vpc   = true

  lifecycle {
    create_before_destroy = true
  }

  tags = {
    Name = "eip"
  }
}

resource "aws_nat_gateway" "ngw" {
  allocation_id = aws_eip.eip.id
  subnet_id = aws_subnet.public_subnet_1.id

  tags = {
    Name = "mb-ngw"
  }
}

# 라우팅 테이블 생성
resource "aws_route_table" "prvroutetable-1" {
  vpc_id = aws_vpc.mb_vpc.id

  tags = {
    Name = "mb-prv-route-table-1"
  }
}

resource "aws_route_table" "prvroutetable-2" {
  vpc_id = aws_vpc.mb_vpc.id

  tags = {
    Name = "mb-prv-route-table-2"
  }
}

# 라우팅 테이블 연결
resource "aws_route_table_association" "prvassociation1" {
  subnet_id      = aws_subnet.private_subnet_1.id
  route_table_id = aws_route_table.prvroutetable-1.id
}

resource "aws_route_table_association" "prvassociation2" {
  subnet_id      = aws_subnet.private_subnet_2.id
  route_table_id = aws_route_table.prvroutetable-2.id
}

resource "aws_route" "prvroute-1" {
  route_table_id         = aws_route_table.prvroutetable-1.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.ngw.id
}

resource "aws_route" "prvroute-2" {
  route_table_id         = aws_route_table.prvroutetable-2.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.ngw.id
}

# 엔드포인트
resource "aws_vpc_endpoint" "endpoint_s3" {
  vpc_id       = aws_vpc.mb_vpc.id
  service_name = "com.amazonaws.ap-northeast-2.s3"
}

resource "aws_vpc_endpoint" "endpoint_ecr_dkr" {
  vpc_id       = aws_vpc.mb_vpc.id
  service_name = "com.amazonaws.ap-northeast-2.ecr.dkr"
  vpc_endpoint_type = "Interface"
}

resource "aws_vpc_endpoint" "endpoint_ecr_api" {
  vpc_id       = aws_vpc.mb_vpc.id
  service_name = "com.amazonaws.ap-northeast-2.ecr.api"
  vpc_endpoint_type = "Interface"
}

resource "aws_vpc_endpoint" "endpoint_log" {
  vpc_id       = aws_vpc.mb_vpc.id
  service_name = "com.amazonaws.ap-northeast-2.logs"
  vpc_endpoint_type = "Interface"
}

resource "aws_vpc_endpoint" "endpoint_ssm" {
  vpc_id       = aws_vpc.mb_vpc.id
  service_name = "com.amazonaws.ap-northeast-2.ssm"
  vpc_endpoint_type = "Interface"
}

 

security_gruop.tf

resource "aws_security_group" "mb_security_gruop" {
  vpc_id = aws_vpc.mb_vpc.id
  name = "mb_security_gruop"
  description = "mb_security_gruop"

  tags = {
        Name = "final-sg-by-tf"
    }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "TCP"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "TCP"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 9000
    to_port     = 9000
    protocol    = "TCP"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    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"]
  }

}

 

iam.tf

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

variable "policy_arn" {
  default = [
    "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
    "arn:aws:iam::aws:policy/SecretsManagerReadWrite"
  ]
}

resource "aws_iam_role_policy_attachment" "role_policy_attachment" {
  role       = aws_iam_role.mb_ecsIAM.name

  count = length(var.policy_arn)
  policy_arn = var.policy_arn[count.index]
}

 

alb.tf

resource "aws_lb_target_group" "mb-target-group" {
  name        = "mb-target-group"
  port        = 80
  protocol    = "HTTP"
  vpc_id = aws_vpc.mb_vpc.id

  target_type = "ip"
}

resource "aws_lb" "mb-alb" {
  name = "mb-alb"
  load_balancer_type = "application"
  subnets = [aws_subnet.public_subnet_1.id, aws_subnet.public_subnet_2.id]
  security_groups = [aws_security_group.mb_security_gruop.id]
}

resource "aws_lb_listener" "mb_listener" {
  load_balancer_arn = aws_lb.mb-alb.arn
  port = 80
  protocol = "HTTP"

  default_action {
    target_group_arn = aws_lb_target_group.mb-target-group.arn
    type = "forward"
  }
}

 

cloudwatch.tf

resource "aws_cloudwatch_log_group" "mb_log" {
  name = "/ecs/mb_container"
  retention_in_days = 7
}

 

dynamodb.tf

resource "aws_dynamodb_table" "marketboro" {
 name = "marketboro"
 billing_mode = "PROVISIONED"
 read_capacity= "30"
 write_capacity= "30"
 attribute {
  name = "id"
  type = "N"
 }
 hash_key = "id"
}

 

task_definition.tf

resource "aws_ecs_task_definition" "mb_definition" {
  family = "mb_definition"
  network_mode = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu = 1024
  memory = 2048
  execution_role_arn = aws_iam_role.mb_ecsIAM.arn
  task_role_arn = aws_iam_role.mb_ecsIAM.arn

  container_definitions = jsonencode([{
        essential = true
        name = "mb_container"
        image = "177938377658.dkr.ecr.ap-northeast-2.amazonaws.com/marketboro:latest"
        cpu = 0

        portMappings = [
          {
            name = "mb_container-9000-tcp"
            protocol = "tcp"
            appProtocol = "http"
            containerPort = 9000
            hostPort = 9000
          }
        ]

        logConfiguration = {
          logDriver = "awslogs",
          options = {
            awslogs-group = "/ecs/mb_container"
            awslogs-region = "ap-northeast-2"
            awslogs-stream-prefix = "ecs"
          }
        }
        secrets= [
                {
                    name= "AWS_ACCESS_KEY_ID",
                    valueFrom= "arn:aws:secretsmanager:ap-northeast-2:177938377658:secret:access_key-CCfz3S:ACCESS_KEY::"
                    secretOptions = [
                      {
                        name  = "AutomationLambdaInvoke"
                        value = "Disabled"
                      }
                    ]
                },
                {
                    name= "AWS_SECRET_ACCESS_KEY",
                    valueFrom= "arn:aws:secretsmanager:ap-northeast-2:177938377658:secret:access_key-CCfz3S:SECRET_KEY::"
                    secretOptions = [
                      {
                        name  = "AutomationLambdaInvoke"
                        value = "Disabled"
                      }
                    ]
                }
        ]
      }
    ]
  )
}

 

ecs.tf

resource "aws_ecs_cluster" "mb_cluster" {
  name = "mb_cluster"
}

resource "aws_ecs_service" "mb_service" {
  name = "mb-service"
  cluster = aws_ecs_cluster.mb_cluster.id
  task_definition = aws_ecs_task_definition.mb_definition.id
  desired_count = 1
  launch_type = "FARGATE"
  enable_execute_command = true

  network_configuration {
    subnets = [aws_subnet.public_subnet_1.id, aws_subnet.public_subnet_2.id]
    security_groups = [aws_security_group.mb_security_gruop.id]
    assign_public_ip = true
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.mb-target-group.arn
    container_name = "mb_container"
    container_port = 9000
  }

  depends_on = [ aws_lb_listener.mb_listener ]
}