Code States/TIL

[0515-0516] (페어) Infrastructure as Code - Terraform x AWS

ki1111m2 2023. 5. 16. 15:39

Full Stack 애플리케이션 구성

 

Bare Minimum Requirement

다음의 아키텍처를 terraform을 이용해 작성합니다.


STEP 1: 자습서: DB 인스턴스에 사용할 Amazon VPC 생성

  1. VPC 및 서브넷 생성
    • 프라이빗 서브넷과 퍼블릭 서브넷이 각각 두 개, 총 네 개가 있어야 합니다.
  2. VPC 보안 그룹 생성
    • 퍼블릭 웹 서버가 사용할 VPC 보안 그룹을 만들어야 합니다.
    • 프라이빗 DB 웹 서버가 사용할 VPC 보안 그룹을 만들어야 합니다.
  3. DB 서브넷 그룹 생성
    • RDS 인스턴스가 사용할 VPC 서브넷 그룹을 만들어야 합니다.

STEP 2: EC2 인스턴스 생성

만들어야 하는 사양은 다음과 같습니다.

  • AMI: Ubuntu Server 18
  • 인스턴스 타입: t2.micro
  • 사용자 데이터
    #!/bin/bash
    echo "Hello, World" > index.html
    nohup busybox httpd -f -p ${var.server_port} &
  • 키 페어: 수동으로 만들고 EC2에 할당합니다.

Advanced Challenges

STEP 3: 자습서: DB 인스턴스 생성

  • 자습서에 표시된 사양대로 RDS 인스턴스를 생성합니다.

STEP 4: 애플리케이션 로드 밸런서 및 Auto Scaling Group 적용

  • Auto Scaling Group은 최소 2개, 최대 10개로 설정해 놓습니다.

최종 코드

main.tf

terraform {
    required_providers {
        aws = {
            source = "hashicorp/aws"
            version = "~> 4.16"
        }
    }
}

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

vpc.tf

//vpc 생성
resource "aws_vpc" "terraform_vpc" {
    cidr_block = "10.0.0.0/16"
    tags = {
        Name = "terraform_vpc"
    }
}

 

variable.tf

variable "server_port" {
  default = 8080
}

variable "username" {
  default = "admin"
}

variable "password" {
  default = "1234qwer"
}

 

subnet.tf

//퍼블릭 서브넷 생성
resource "aws_subnet" "terraform_public_subnet1" {
    vpc_id = aws_vpc.terraform_vpc.id
    cidr_block = "10.0.1.0/24"
    availability_zone = "ap-northeast-2a"
    map_public_ip_on_launch = true
    tags = {
        Name = "public_subnet1"
    }
}

resource "aws_subnet" "terraform_public_subnet2" {
    vpc_id = aws_vpc.terraform_vpc.id
    cidr_block = "10.0.2.0/24"
    availability_zone = "ap-northeast-2c"
    map_public_ip_on_launch = true
    tags = {
        Name = "public_subnet2"
    }
}

//프라이빗 서브넷 생성
resource "aws_subnet" "terraform_private_subnet1" {
    vpc_id = aws_vpc.terraform_vpc.id
    cidr_block = "10.0.3.0/24"
    availability_zone = "ap-northeast-2a"
    tags = {
        Name = "private_subnet1"
    }
}

resource "aws_subnet" "terraform_private_subnet2" {
    vpc_id = aws_vpc.terraform_vpc.id
    cidr_block = "10.0.4.0/24"
    availability_zone = "ap-northeast-2c"
    tags = {
        Name = "private_subnet2"
    }
}

 

route_table.tf

//퍼블릭 서브넷 라우팅 테이블 생성 및 인터넷 게이트웨이 연결
//인터넷 게이트웨이 생성
resource "aws_internet_gateway" "terraform_igw" {
    vpc_id = aws_vpc.terraform_vpc.id
    tags = {
      Name = "terraform-igw"
    }
}

//라우팅 테이블 생성
resource "aws_route_table" "terraform_public_rtb" {
    vpc_id = aws_vpc.terraform_vpc.id
    route {
        cidr_block = "0.0.0.0/0"
        gateway_id = aws_internet_gateway.terraform_igw.id
    }
    tags = {
      Name = "public_rtb"
    }
}

//라우팅 테이블 연결
resource "aws_route_table_association" "terraform_public_rtb_association1" {
    subnet_id = aws_subnet.terraform_public_subnet1.id
    route_table_id = aws_route_table.terraform_public_rtb.id
}

resource "aws_route_table_association" "terraform_public_rtb_association2" {
    subnet_id = aws_subnet.terraform_public_subnet2.id
    route_table_id = aws_route_table.terraform_public_rtb.id
}


//프라이빗 서브넷 라우팅 테이블 생성 및 NAT 게이트웨이 연결
//NAT 게이트웨이 생성
resource "aws_eip" "terraform_eip" {
    vpc = true
    lifecycle {
      create_before_destroy = true
    }
}

resource "aws_nat_gateway" "terraform_ngw" {
    allocation_id = aws_eip.terraform_eip.id
    subnet_id = aws_subnet.terraform_private_subnet1.id
    tags = {
        Name = "terraform_ngw"
    }
}

//라우팅 테이블 생성
resource "aws_route_table" "terraform_private_rtb" {
    vpc_id = aws_vpc.terraform_vpc.id
    tags = {
        Name = "terraform_private_rtb"
    }
}

//라우팅 테이블 연결
resource "aws_route_table_association" "terraform_private_rtb_association1" {
    subnet_id = aws_subnet.terraform_private_subnet1.id
    route_table_id = aws_route_table.terraform_private_rtb.id
}

resource "aws_route_table_association" "terraform_private_rtb_association2" {
    subnet_id = aws_subnet.terraform_private_subnet2.id
    route_table_id = aws_route_table.terraform_private_rtb.id
}

resource "aws_route" "terraform_private_route" {
    route_table_id = aws_route_table.terraform_private_rtb.id
    destination_cidr_block = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.terraform_ngw.id
}

 

security_gruop.tf

//퍼블릭 보안 그룹 생성
resource "aws_security_group" "terraform_public_sg" {
    vpc_id = aws_vpc.terraform_vpc.id
    name = "terraform_public_sg"
    description = "terraform_public_sg"
    tags = {
        Name = "terraform_public_sg"
    }
}

//퍼블릭 보안 그룹 규칙
resource "aws_security_group_rule" "terraform_public_sg_http1" {
    type = "ingress"
    from_port = 80
    to_port = 80
    protocol = "TCP"
    cidr_blocks = ["0.0.0.0/0"]
    security_group_id = aws_security_group.terraform_public_sg.id
    lifecycle {
        create_before_destroy = true
    }
}

resource "aws_security_group_rule" "terraform_public_sg_http2" {
    type = "ingress"
    from_port = 8080
    to_port = 8080
    protocol = "TCP"
    cidr_blocks = ["0.0.0.0/0"]
    security_group_id = aws_security_group.terraform_public_sg.id
    lifecycle {
        create_before_destroy = true
    }
}

resource "aws_security_group_rule" "terraform_public_sg_ssh" {
    type = "ingress"
    from_port = 22
    to_port = 22
    protocol = "TCP"
    cidr_blocks = ["0.0.0.0/0"]
    security_group_id = aws_security_group.terraform_public_sg.id
    lifecycle {
        create_before_destroy = true
    }
}

resource "aws_security_group_rule" "terraform_public_sg_all" {
    type = "egress"
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    security_group_id = aws_security_group.terraform_public_sg.id
    lifecycle {
        create_before_destroy = true
    }
}



//프라이빗 보안 그룹 새성
resource "aws_security_group" "terraform_private_sg" {
    vpc_id = aws_vpc.terraform_vpc.id
    name = "terraform_private_sg"
    description = "terraform_private_sg"
    tags = {
        Name = "terraform_private_sg"
    }
}

//프라이빗 보안 그룹 규칙
resource "aws_security_group_rule" "terraform_private_sg_rds" {
    type = "ingress"
    from_port = 3306
    to_port = 3306
    protocol = "TCP"
    cidr_blocks = ["0.0.0.0/0"]
    security_group_id = aws_security_group.terraform_private_sg.id
    lifecycle {
        create_before_destroy = true
    }
}

resource "aws_security_group_rule" "terraform_private_sg_all" {
    type = "egress"
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    security_group_id = aws_security_group.terraform_private_sg.id
    lifecycle {
        create_before_destroy = true
    }
}

 

ec2.tf (advanced 단계부턴 auto sciling으로 생성하기 때문에 주석처리함)

/*
//키페어 생성
resource "aws_key_pair" "terraform_key" {
    key_name = "terraform_key"
    public_key = file("./terraform_key.pub")
}

//인스턴스 생성
resource "aws_instance" "terraform_ec2_1" {
    ami = "ami-0738b2579e0d5ec7d"
    instance_type = "t2.micro"
    security_groups = [aws_security_group.terraform_public_sg.id]
    subnet_id = aws_subnet.terraform_public_subnet1.id
    key_name = aws_key_pair.terraform_key.key_name
    associate_public_ip_address = "true"
    user_data = <<-EOF
        #!/bin/bash
        echo "Hello, World" > index.html
        nohup busybox httpd -f -p ${var.server_port} &
        EOF
    tags = {
        Name = "terraform_ec2_1"
    }
}

resource "aws_instance" "terraform_ec2_2" {
    ami = "ami-0738b2579e0d5ec7d"
    instance_type = "t2.micro"
    security_groups = [aws_security_group.terraform_public_sg.id]
    subnet_id = aws_subnet.terraform_public_subnet2.id
    key_name = aws_key_pair.terraform_key.key_name
    associate_public_ip_address = "true"
    user_data = <<-EOF
        #!/bin/bash
        echo "Hello, World" > index.html
        nohup busybox httpd -f -p ${var.server_port} &
        EOF
    tags = {
        Name = "terraform_ec2_2"
    }
}
*/

 

rds.tf

//DB 서브넷 그룹 생성
resource "aws_db_subnet_group" "terraform_db_subnetgroup" {
    name = "terraform_db_subnetgroup"
    subnet_ids = [
        aws_subnet.terraform_private_subnet1.id,
        aws_subnet.terraform_private_subnet2.id
    ]
    tags = {
        Name = "terraform_db_subnetgroup"
    }
}

//RDS 생성
resource "aws_db_instance" "terraform_rds" {
    allocated_storage = 20
    skip_final_snapshot = true
    availability_zone = "ap-northeast-2a"
    vpc_security_group_ids = [aws_security_group.terraform_private_sg.id]
    db_subnet_group_name = aws_db_subnet_group.terraform_db_subnetgroup.name
    engine = "mysql"
    engine_version = "8.0.28"
    instance_class = "db.t3.micro"
    db_name = "terraform_rds"
    username = var.username
    password = var.password
    tags = {
        Name = "terraform_db_subnetgroup"
    }
}

 

alb.tf (주석처리한 부분은 ec2 생성)

//alb 생성
resource "aws_alb" "terraform_alb" {
    name = "terraform-alb"
    internal = false
    load_balancer_type = "application"
    security_groups = [aws_security_group.terraform_public_sg.id]
    subnets = [
        aws_subnet.terraform_public_subnet1.id,
        aws_subnet.terraform_public_subnet2.id
    ]
    enable_cross_zone_load_balancing = true
}

//타겟그룹 생성
resource "aws_alb_target_group" "terraform_alb_tg" {
    name = "terraform-alb-tg"
    port = 8080
    protocol = "HTTP"
    vpc_id = aws_vpc.terraform_vpc.id
}

/*
//인스턴스 연결
resource "aws_alb_target_group_attachment" "terraform_alb_tg_atc1" {
    target_group_arn = aws_alb_target_group.terraform_alb_tg.arn
    target_id = aws_instance.terraform_ec2_1.id
    port = 8080
}

resource "aws_alb_target_group_attachment" "terraform_alb_tg_atc2" {
    target_group_arn = aws_alb_target_group.terraform_alb_tg.arn
    target_id = aws_instance.terraform_ec2_2.id
    port = 8080
}
*/

resource "aws_autoscaling_attachment" "terraform_autoscaling_attachment" {
    autoscaling_group_name = aws_autoscaling_group.terraform_autoscailinggruop.name
    lb_target_group_arn   = aws_alb_target_group.terraform_alb_tg.arn
}


//리스너 생성
resource "aws_alb_listener" "terraform_alb_listener" {
    load_balancer_arn = aws_alb.terraform_alb.arn
    port = 80
    protocol = "HTTP"
    default_action {
      type = "forward"
      target_group_arn = aws_alb_target_group.terraform_alb_tg.arn
    }
}

 

auto_scailing.tf

//역할 생성
resource "aws_iam_role" "autoscaling_role" {
  name = "autoscaling-role"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "autoscaling.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

//정책 설정
resource "aws_iam_role_policy" "autoscaling_policy" {
  name = "autoscaling-policy"
  role = aws_iam_role.autoscaling_role.id

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "elasticloadbalancing:Describe*",
        "elasticloadbalancing:RegisterTargets",
        "elasticloadbalancing:DeregisterTargets"
      ],
      "Resource": "*"
    }
  ]
}
EOF
}


//키페어 생성
resource "aws_key_pair" "terraform_key" {
    key_name = "terraform_key"
    public_key = file("./terraform_key.pub")
}


//시작 구성 생성
resource "aws_launch_configuration" "terraform_launch" {
    name_prefix = "laun-"
    image_id = "ami-0738b2579e0d5ec7d"
    instance_type = "t2.micro"
    security_groups = [aws_security_group.terraform_public_sg.id]
    key_name = aws_key_pair.terraform_key.key_name
    user_data = <<-EOF
        #!/bin/bash
        echo "Hello, World" > index.html
        nohup busybox httpd -f -p ${var.server_port} &
        EOF
    lifecycle {
      create_before_destroy = true
    }
}

//Auto Scailing 그룹 생성
resource "aws_autoscaling_group" "terraform_autoscailinggruop" {
    
    name = "terraform_autoscailinggruop"
    launch_configuration = aws_launch_configuration.terraform_launch.name
    vpc_zone_identifier = [
        aws_subnet.terraform_public_subnet1.id,
        aws_subnet.terraform_public_subnet2.id
    ]
    health_check_type = "ELB"
    target_group_arns = [aws_alb_target_group.terraform_alb_tg.arn]
    force_delete = true

    min_size = 2
    max_size = 10

    tag {
        key = "Name"
        value = "terraform_autoscailing"
        propagate_at_launch = true
    }
}

 


트러블슈팅

Error: error creating Route Table (rtb-0f74c3f80fc7ae02d) Association: Resource.AlreadyAssociated: the specified association for route table rtb-0f74c3f80fc7ae02d conflicts with an existing association

라우트 테이블 연결시 발생한 오류

프라이빗과 퍼블릭 라우트 테이블 연결에 모두 프라이빗 라우트 테이블을 연결했다.. ㅎㅎ;; 

퍼블릭으로 수정 후 해결

Error: Reference to undeclared input variable

변수를 찾지 못하는 오류 발생

variable.tf 파일 생성하여 환경변수 추가 후 해결

Error: Invaild function argument

키를 못찾는 오류 발생

 

콘솔에서 수동으로 키 생성

폴더에 키 추가

생성한 키의 형식에 맞게 코드 수정 pub->pem

이번엔 이미 존재하는 키페어라고 한다..

키페어 생성하는 부분을 지우고 생성하는 부분에서 바로 호출

이번엔 존재하지 않는다고 한다 

콘솔에서 생성하는 키가 안먹나 싶어서 터미널에서 바로 생성해줬다

정상 작동

인스턴스가 성공적으로 생성되었다

 

Error: only alphanumeric chacters and hyphens allowed in "name"

다른 리소스에서는 이름에 언더바 쓸 수 있었는데 얘는 안된단다.. ;;

3번 라인과 16번 라인의 name에서 언더바를 대시로 바꾼 후 해결

 

Error: Incorrect attribute value type

id 값이 필요한데 변수 뒤에 .id를 안붙여서 발생한 오류

뒷부분에 .id를 붙여서 해결

 

Error: waiting for Auto Scaling Group (terraform_autoscailinggruop) capacity satisfied: 2 errors occurred:

* Scaling activity (92c6207f-0803-035d-c7f1-fe42100ebfa7): Failed: Access denied when attempting to assume role arn:aws:iam::477644685304:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling. Validating load balancer configuration failed.

* Scaling activity (b046207f-0801-850a-fe69-ecd8a2e9a52e): Failed: Access denied when attempting to assume role arn:aws:iam::477644685304:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling. Validating load balancer configuration failed.

오토 스캐일링 그룹에서 권한이 없어서 발생한 문제

역할과 정책 생성

정상 작동

오토스캐일링 정상 작동 확인

그 외에도 테라폼을 이용해서 만든 리소스들이 정상적으로 생성된 것을 확인할 수 있다

alb 주소로 접근 시 Hello, world까지 뜨는 것을 확인할 수 있다

이제 EC2 생성 파일을 주석처리하고 오토스케일링으로만 동작하게 만들어야 한다

키 페어 설정을 오토스케일링으로 옮기고, alb와 오토스케일링 연결 부분을 추가한다

Error: registering targets with target group: ValidationError: Instance ID 'terraform_autoscailinggruop' is not valid

인스턴스 아이디를 인식할 수 없는 오류 발생

아래 코드와 같이 변경

정상 작동은 하지만 아래와 같은 경고가 발생했다

 

Warning: Argument is deprecated

│ with aws_autoscaling_attachment.terraform_autoscaling_attachment,

│ on alb.tf line 45, in resource "aws_autoscaling_attachment" "terraform_autoscaling_attachment":

│ 45: alb_target_group_arn = aws_alb_target_group.terraform_alb_tg.arn

│ Use lb_target_group_arn instead

터미널의 충고와 같이 alb를 lb로 변경했다

경고 없이 정상 작동 한다

ec2 없이 오토스케일링으로만 인스턴스가 정상적으로 생성된다

오토스캐일링으로 연결된 alb로 접속해도 헬로 월드가 정상적으로 뜬다