본문 바로가기

♻ Terraform(테라폼)/Terraform 활용

테라폼을 활용한 웹서버 클러스터(ASG) 배포

ASG: Auto Scaling Group

ASG는 EC2 인스턴스 클러스터의 시작, 각 인스턴스 상태 모니터링, 실패한 인스턴스 교체, 로드에 따른 클러스터 사이즈 조정 등 많은 작업을 자동으로 처리 가능.

ASG를 만드는 첫번째 단계는 시작 구성(launch configuration)을 만드는 것부터 시작한다.

launch configuration과 Auto Scaling Group을 terraform 코드로 구현

# Luanch Configuration 설정
resource "aws_launch_configuration" "example" {
  image_id        = "ami-0ed11f3863410c386"
  instance_type   = "t2.micro"
  security_groups = [aws_security_group.instance.id]

  user_data = <<-EOF
		#!/bin/bash
		echo "Hello, World" > index.html
		nohup busybox httpd -f -p 8080 &
		EOF
}

# AWS AutoScaling Group 생성
resource "aws_autoscaling_group" "example" {
  launch_configuration = aws_launch_configuration.example.name

  min_size = 1
  max_size = 3

  tag {
    key                 = "Name"
    value               = "terraform-asg-example"
    propagate_at_launch = true
  }
}

위의 ASG는 최소 1개~최대2개의 인스턴스가 생성되도록 구성 되었으며, 각각 terraform-asg-example 이라는 이름으로 태그가 지정 된다. ASG는 시작구성 정보를 참조하여 인스턴스를 생성 하는데, 여기서 한가지 문제가 발생한다.

시작구성 즉 launch configuration은 변경할 수 없으므로 시작 구성 정보를 변경하면 테라폼은 이를 대체하려고 한다. 일반적으로 리소스를 교체할때 테라폼은 이전 리소스를 먼저 삭제 후 대체 리소스를 생성한다. 그러나 ASG에 이전 리소스에 대한 참조정보가 존재하기 때문에 테라폼은 해당 리소스를 삭제할수 없게 된다.

이 문제를 해결하기 위해 테라폼은 수명주기(lifecycle)설정을 지원한다. 특히 create_before_destroy는 유용하게 사용할 수 있는 수명주기 설정인데 해당 설정을 true로 하게되면 테라폼은 리소스 교체 순서를 반대로 하여 교체 리소스를 먼저 생성 후 기존 리소스를 삭제 한다. 아래와 같이 launch configuration에 create_before_destroy옵션을 추가해 본다.

ASG 생성 부분 코드 (create_before_destroy옵션 추가)

# AWS AutoScaling Group 생성
resource "aws_autoscaling_group" "example" {
  launch_configuration = aws_launch_configuration.example.name

  min_size = 1
  max_size = 3

  tag {
    key                 = "Name"
    value               = "terraform-asg-example"
    propagate_at_launch = true
  }
  ## ASG에서 시작 구성을 사용할때 필요한 옵션: 최신 리소스를 선 생성 후 기존 리소스를 삭제
  lifecycle {
    create_before_destroy = true
  }
}

ASG에 추가해야 작동하는 subnet_ids 값도 존재 한다. 서브넷은 ASG가 어느 EC2를 어느 VPC의 서브넷에 배포할지 지정하는 값이다. 각각의 서브넷은 AWS AZ(서로 분리된 데이터 센터에 존재)에 있으므로 인스턴스를 여러개의 서브넷에 배포할 경우 일부 데이터 센터가 중단된 경우에도 서비스를 유지할 수 있다는 장점이 있다.

서브넷 목록을 하드코딩 하여 사용할 수 있지만, 유지 관리가 어렵고 이식이 불가능 하므로 데이터 소스를 사용하여 AWS 계정에서 서브넷 목록을 얻는것이 바람직한 방법이다.

여기서 데이터 소스란 테라폼을 실행할때 공급자 즉 AWS에서 가져온 정보를 나타낸다. 예를들어 AWS는 VPC데이터, 서브넷 데이터, AMI ID, IP 주소범위, 현재 사용자의 자격증명 등을 조회하는 데이터 소스가 포함되어 있다.

변수의 선언과 VPC설정 및 Subnet 설정

# 변수를 선언: 서버포트
variable "server_port" {
  description = "The port the server will use for HTTP requests"
  type        = number
  default     = 8080
}

# VPC 설정
data "aws_vpc" "default" {
  default = true
}

# 서브넷 설정
data "aws_subnet_ids" "default" {
  vpc_id = data.aws_vpc.default.id
}

데이터 소스를 사용하는 구문

데이터 소스를 사용하는 구문은 리소스 구문과 매우 유사하다.

data "<PROVIDER>_<TYPE>" "<NAME>" {
	[CONFIG ...]
}

위의 코드에서 <PROVIDER>는 AWS와 같은 공급자이고, <TYPE>은 VPC와 같은 사용하려는 데이터 소스의 유형이며 <NAME>은 테라폼 코드에서 이 데이터 소스를 참조하는데 사용할 수 있는 식별 값 이다.

데이터 소스에서 데이터를 가져오는 방법

data.<PROVIDER>_<TYPE>.<NAME>.<ATTRIBUTE>

예를들어 aws_vpc 데이터 소스에서 VPC의 ID를 얻으려면 속성 참조 구문을 아래와 같이 작성하여야 한다.

data.aws_vpc.default.id

위에서 얻은 VPC ID 값을 다른 데이터 소스인 aws_subnet_ids와 결합하여 해당 VPC 내 서브넷을 조회 할 수 있다.

data "aws_subnet_ids" "default" {
 vpc_id = data.aws_vpc.default.id
}

마지막으로 ASG에 subnet을 사용하도록 설정

vpc_zone_identifier 인수를 이용해 aws_subnet_ids 데이터 소스에서 서브넷ID를 가져와서 ASG가 이 서브넷을 사용하도록 구현해보자.

vpc_zone_identifier = data.aws_subnet_ids.default.ids

ASG 생성코드 vpc_zone_identifier값 추가

# AWS AutoScaling Group 생성
resource "aws_autoscaling_group" "example" {
  launch_configuration = aws_launch_configuration.example.name
  vpc_zone_identifier = data.aws_subnet_ids.default.ids

  min_size = 1
  max_size = 3

  tag {
    key                 = "Name"
    value               = "terraform-asg-example"
    propagate_at_launch = true
  }
  ## ASG에서 시작 구성을 사용할때 필요한 옵션: 최신 리소스를 선 생성 후 기존 리소스를 삭제
  lifecycle {
    create_before_destroy = true
  }
}

로드 밸런서 배포

로드 밸런서는 아마존에서 제공하는 ELB서비스를 하용하여 AWS 제공차가 처리 하도록 한다.

AWS는 3가지 유형의 LB를 제공한다. ALB, NLB, CLB 자세한 내용은 아래 링크를 참조

2022.04.14 - [AWS/ELB (ALB, NLB, CLB)] - ALB, NLB, CLB의 개념

 

ALB, NLB, CLB의 개념

AWS에서는 ALB, NLB, CLB를 통틀어 ELB 즉 ElasticLoadBalancer라고 부른다. 오늘날 대부분의 응용 프로그램은 ALB 또는 NLB를 사용해야 한다. 테스트 용도로 사용하는 간단한 예제는 성능 요구사항이 까다롭

may9noy.tistory.com

ALB의 구성은 아래의 내용처럼 리스너, 리스너규칙, 대상그룹 으로 구성 된다.

리스너(Listener): 80과 같은 특정 포트와 HTTP 같은 프로토콜을 수신

리스너 규칙 (Listener rule): 리스너에 들어오는 요청을 가져와 /foo 및 /bar 같은 특정 경로나 foo.example.com 및 bar.example.com 같은 호스트 이름과 일치하는 요청을 특정 대상그룹으로 보낸다.

대상그룹 (Target groups): 로드 밸런서에서 요청을 받는 하나 이상의 서버이다. 대상 그룹은 서버의 상태를 확인하고 요청을 정상 노드로 보낸다.

1. ALB 리소스를 생성

aws_lb 리소르를 기존의 terraform 코드에 추가 합니다.

# ALB resource 생성
resource "aws_lb" "example" {
  name               = "terraform-asg-example"
  load_balancer_type = "application"
  subnets            = data.aws_subnet_ids.default.ids

}

여기서 subnets 매개 변수는 aws_subnet_ids 데이터 소스를 사용하여 기본 VPC의 모든 서브넷을 사용하도록 로드 밸런서를 구성한다. AWS는 트래픽에 따라 로드 밸런서 서버수를 자동으로 확장 또는 축소하고 해당 서버 중 하나가 다운되면 장애조치를 활성화 하여 확장성과 가용성을 얻을 수 있다.

2. ALB를 생성하는 두번째 단계는 aws_lb_listener 리소스를 사용하여 ALB 리스너를 정의

# ALB aws_lb_listener 생성
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.example.arn
  port              = 80
  protocol          = "HTTP"
  ## 기본값으로 단순한 404 페이지 오류를 반환
  default_action {
	type = "fixed-response"

	fixed_response {
	  content_type = "text/plain"
	  message_body = "404: page not found"
	  status_code = 404
	}
  }
}

위의 리스너는 기본 HTTP 포트인 80번 포트를 수신하고, HTTP를 프로토콜로 사용 및 리스너 규칙과 일치하지 않는 요청에 대해 기본 응답으로 404페이지를 보내도록 ALB를 구성 한다.

3. ALB의 보안그룹 생성

기본적으로 ALB를 포함한 모든 AWS 리소스는 들어오는 트래픽 및 나가는 트래픽을 허용하지 않는다.

ALB를 위해 특별히 새 보안그룹을 생성 해야 한다. 아래의 보안그룹 리소스는 80번 포트에서 들어오는 요청을 허용하여 HTTP를 통해 로드밸런서에 접속 할 수 있게 한다. 그리고 바깥으로 나가는 요청은 포트와 상관없이 허용, 로드 밸런서가 상태 확인을 수행하도록 한다.

ALB 보안그릅 생성 코드

# ALB의 보안그룹 생성
resource "aws_security_group" "alb" {
	name = "terraform-example-alb"

	## 인바운드 HTTP 트래픽 허용
	ingress {
		from_port = 80
		to_port = 80
		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" ]
	}
}

위의 protocol = "-1" 의 의미는, 모든 프로토콜을 허용한다는 의미로 생각하면 된다.

security_groups 인수를 통해 이 보안 그룹을 사용하려면 aws_lb 리소스에 아래와 같이 security_groups 를 선언해 줘야 한다.

# ALB resource 생성
resource "aws_lb" "example" {
  name               = "terraform-asg-example"
  load_balancer_type = "application"
  subnets            = data.aws_subnet_ids.default.ids
  security_groups = [aws_security_group.alb.id]
}

4. 타켓그룹 생성, aws_lb_target_group 리소스를 사용하여 ASG의 대상 그룹을 생성해야 한다.

타겟그룹 생성 코드

# ALB 타겟그룹 생성
resource "aws_lb_target_group" "asg" {
  name     = "terraform-as-example"
  port     = var.server_port
  protocol = "HTTP"
  vpc_id   = data.aws_vpc.default.id

  health_check {
    path                = "/"
    protocol            = "HTTP"
    matcher             = "200"
    interval            = 15
    timeout             = 3
    healthy_threshold   = 2
    unhealthy_threshold = 2
  }
}

대상 그룹은  각 인스턴스에 주기적으로 HTTP 요청을 전송하여 인스턴스 상태를 점검한다. 위의 구성된 matcher과 일치하는 응답을 반환하는 경우에만 인스턴스를 정상으로 간주 한다. 위와 같이 200 OK 응답을 찾도록 matcher를 구성 할 수 있고, 인스턴스가 다운되거나 오버로드되어 응답하지 않으면 unhealthy로 표시되고 대상그룹은 트래픽을 자동으로 중지 한다.

오토스케일링 그룹과 타겟 그룹 연결

이전에 작성한 aws_autoscaling_group 리소스로 돌아가서 target_group_arns 인수를 설정, 대상그룹을 지정 한다.

여기서 health_check_type을 보면 기본은 EC2인데 여기서는 ELB로 설정 하였다. 기본 설정인 EC2는 인스턴스가 완전히 다운되었다고 판단될 경우에만 인스턴스를 비정상 상태라고 간주 하지만, ELB로 설정하면 ASG가 대상그룹의 상태를 확인하고 비정상이라고 판별될 경우 인스턴스를 자동으로 교체하도록 지시 한다. 이는 인스턴스가 다운되었거나 메모리 부족 및 중요 프로세스 중단이 경우에도 인스턴스가 교체 되기 때문에 EC2 옵션보다 강력하다고 볼 수 있다.

# AWS AutoScaling Group 생성
resource "aws_autoscaling_group" "example" {
  launch_configuration = aws_launch_configuration.example.name
  vpc_zone_identifier  = data.aws_subnet_ids.default.ids

  ## target그룹 연결
  target_group_arns = [aws_lb_target_group.asg.arn]
  health_check_type = "ELB"

  min_size = 1
  max_size = 3

  tag {
    key                 = "Name"
    value               = "terraform-asg-example"
    propagate_at_launch = true
  }
  ## ASG에서 시작 구성을 사용할때 필요한 옵션: 최신 리소스를 선 생성 후 기존 리소스를 삭제
  lifecycle {
    create_before_destroy = true
  }
}

aws_lb_listener_rule 리소스를 사용해 리스너 규칙을 생성하고 모든 부분을 연결 해야한다.

아래의 코드를 terraform 코드에 추가로 생성 한다.

해당 코드의 내용은 모든 경로와 일치하는 요청을 ASG가 포함된 대상 그룹으로 보내는 리스너 규칙을 추가한 것이다.

# ALB with ASG 서비스 연결
resource "aws_lb_listener_rule" "asg" {
  listener_arn = aws_lb_listener.http.arn
  priority     = 100

  condition {
    path_pattern {
      values = ["*"]
    }
  }

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.asg.arn
  }
}

마지막으로 이전의 단일 EC2 인스턴스의 기존 public_ip 출력을 ALB의 DNS 주소로 출력하도록 아래의 내용을 추가 한다.

# ALB DNS 주소를 출력
output "alb_dns_name" {
  value       = aws_lb.example.dns_name
  description = "The domain name of the load balancer"
}

현재까지 main.tf 파일의 전체 소스코드는 아래와 같다.

# 리전 설정
provider "aws" {
  region = "ap-northeast-2"
}

# 보안그룹 설정
resource "aws_security_group" "instance" {
  name = "terraform-example-instance"
  ## 인바운드 설정
  ingress {
    from_port   = 8080
    to_port     = 8080
    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"]
    ipv6_cidr_blocks = ["::/0"]
  }
}

# 인스턴스 생성
resource "aws_instance" "example" {
  ami                    = "ami-0ed11f3863410c386"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.instance.id]

  user_data = <<-EOF
		#!/bin/bash
		echo "Hello, World" > index.html
		nohup busybox httpd -f -p 8080 &
		EOF
  ## 테라폼 인스턴스 이름 및 태그 설정
  tags = {
    Name = "terraform-example"
  }
}

# Luanch Configuration 설정
resource "aws_launch_configuration" "example" {
  image_id        = "ami-0ed11f3863410c386"
  instance_type   = "t2.micro"
  security_groups = [aws_security_group.instance.id]

  user_data = <<-EOF
		#!/bin/bash
		echo "Hello, World" > index.html
		nohup busybox httpd -f -p 8080 &
		EOF
}

# AWS AutoScaling Group 생성
resource "aws_autoscaling_group" "example" {
  launch_configuration = aws_launch_configuration.example.name
  vpc_zone_identifier  = data.aws_subnet_ids.default.ids

  ## target그룹 연결
  target_group_arns = [aws_lb_target_group.asg.arn]
  health_check_type = "ELB"

  min_size = 1
  max_size = 3

  tag {
    key                 = "Name"
    value               = "terraform-asg-example"
    propagate_at_launch = true
  }
  ## ASG에서 시작 구성을 사용할때 필요한 옵션: 최신 리소스를 선 생성 후 기존 리소스를 삭제
  lifecycle {
    create_before_destroy = true
  }
}

# 변수를 선언: 서버포트
variable "server_port" {
  description = "The port the server will use for HTTP requests"
  type        = number
  default     = 8080
}

# VPC 설정
data "aws_vpc" "default" {
  default = true
}

# 서브넷 설정
data "aws_subnet_ids" "default" {
  vpc_id = data.aws_vpc.default.id
}

# ALB resource 생성
resource "aws_lb" "example" {
  name               = "terraform-asg-example"
  load_balancer_type = "application"
  subnets            = data.aws_subnet_ids.default.ids
  security_groups    = [aws_security_group.alb.id]
}

# ALB aws_lb_listener 생성
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.example.arn
  port              = 80
  protocol          = "HTTP"
  ## 기본값으로 단순한 404 페이지 오류를 반환
  default_action {
    type = "fixed-response"

    fixed_response {
      content_type = "text/plain"
      message_body = "404: page not found"
      status_code  = 404
    }
  }
}

# ALB의 보안그룹 생성
resource "aws_security_group" "alb" {
  name = "terraform-example-alb"

  ## 인바운드 HTTP 트래픽 허용
  ingress {
    from_port   = 80
    to_port     = 80
    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"]
  }
}

# ALB 타겟그룹 생성
resource "aws_lb_target_group" "asg" {
  name     = "terraform-asg-example"
  port     = "${var.server_port}"
  protocol = "HTTP"
  vpc_id   = data.aws_vpc.default.id

  health_check {
    path                = "/"
    protocol            = "HTTP"
    matcher             = "200"
    interval            = 15
    timeout             = 3
    healthy_threshold   = 2
    unhealthy_threshold = 2
  }
}

# ALB with ASG 서비스 연결
resource "aws_lb_listener_rule" "asg" {
  listener_arn = aws_lb_listener.http.arn
  priority     = 100

  condition {
    path_pattern {
      values = ["*"]
    }
  }

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.asg.arn
  }
}

# ALB DNS 주소를 출력
output "alb_dns_name" {
  value       = aws_lb.example.dns_name
  description = "The domain name of the load balancer"
}

이제 해당 파일을 terraform plan을 통해 확인해 보자.

C:\Users\hist\terraform>terraform plan
aws_security_group.instance: Refreshing state... [id=...]
aws_instance.example: Refreshing state... [id=...]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_autoscaling_group.example will be created
  + resource "aws_autoscaling_group" "example" {
      + arn                       = (known after apply)
      + availability_zones        = (known after apply)
      + default_cooldown          = (known after apply)
      + desired_capacity          = (known after apply)
      + force_delete              = false
      + force_delete_warm_pool    = false
      + health_check_grace_period = 300
      + health_check_type         = "ELB"
      + id                        = (known after apply)
      + launch_configuration      = (known after apply)
      + max_size                  = 3
      + metrics_granularity       = "1Minute"
      + min_size                  = 1
      + name                      = (known after apply)
      + name_prefix               = (known after apply)
      + protect_from_scale_in     = false
      + service_linked_role_arn   = (known after apply)
      + target_group_arns         = (known after apply)
      + vpc_zone_identifier       = [
          + "subnet-6ce20923",
          + "subnet-9b8403f0",
          + "subnet-e45194bb",
        ]
      + wait_for_capacity_timeout = "10m"

      + tag {
          + key                 = "Name"
          + propagate_at_launch = true
          + value               = "terraform-asg-example"
        }
    }

  # aws_launch_configuration.example will be created
  + resource "aws_launch_configuration" "example" {
      + arn                         = (known after apply)
      + associate_public_ip_address = false
      + ebs_optimized               = (known after apply)
      + enable_monitoring           = true
      + id                          = (known after apply)
      + image_id                    = "ami-0ed11f3863410c386"
      + instance_type               = "t2.micro"
      + key_name                    = (known after apply)
      + name                        = (known after apply)
      + name_prefix                 = (known after apply)
      + security_groups             = [
          + "sg-00c6a7f6821e544df",
        ]
      + user_data                   = "67e34b406ab639a606a64fe06965b26bf8036a9c"

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + no_device             = (known after apply)
          + snapshot_id           = (known after apply)
          + throughput            = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + throughput            = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # aws_lb.example will be created
  + resource "aws_lb" "example" {
      + arn                        = (known after apply)
      + arn_suffix                 = (known after apply)
      + desync_mitigation_mode     = "defensive"
      + dns_name                   = (known after apply)
      + drop_invalid_header_fields = false
      + enable_deletion_protection = false
      + enable_http2               = true
      + enable_waf_fail_open       = false
      + id                         = (known after apply)
      + idle_timeout               = 60
      + internal                   = (known after apply)
      + ip_address_type            = (known after apply)
      + load_balancer_type         = "application"
      + name                       = "terraform-asg-example"
      + security_groups            = (known after apply)
      + subnets                    = [
          + "subnet-6ce20923",
          + "subnet-9b8403f0",
          + "subnet-e45194bb",
        ]
      + tags_all                   = (known after apply)
      + vpc_id                     = (known after apply)
      + zone_id                    = (known after apply)

      + subnet_mapping {
          + allocation_id        = (known after apply)
          + ipv6_address         = (known after apply)
          + outpost_id           = (known after apply)
          + private_ipv4_address = (known after apply)
          + subnet_id            = (known after apply)
        }
    }

  # aws_lb_listener.http will be created
  + resource "aws_lb_listener" "http" {
      + arn               = (known after apply)
      + id                = (known after apply)
      + load_balancer_arn = (known after apply)
      + port              = 80
      + protocol          = "HTTP"
      + ssl_policy        = (known after apply)
      + tags_all          = (known after apply)

      + default_action {
          + order = (known after apply)
          + type  = "fixed-response"

          + fixed_response {
              + content_type = "text/plain"
              + message_body = "404: page not found"
              + status_code  = "404"
            }
        }
    }

  # aws_lb_listener_rule.asg will be created
  + resource "aws_lb_listener_rule" "asg" {
      + arn          = (known after apply)
      + id           = (known after apply)
      + listener_arn = (known after apply)
      + priority     = 100
      + tags_all     = (known after apply)

      + action {
          + order            = (known after apply)
          + target_group_arn = (known after apply)
          + type             = "forward"
        }

      + condition {

          + path_pattern {
              + values = [
                  + "*",
                ]
            }
        }
    }

  # aws_lb_target_group.asg will be created
  + resource "aws_lb_target_group" "asg" {
      + arn                                = (known after apply)
      + arn_suffix                         = (known after apply)
      + connection_termination             = false
      + deregistration_delay               = "300"
      + id                                 = (known after apply)
      + lambda_multi_value_headers_enabled = false
      + load_balancing_algorithm_type      = (known after apply)
      + name                               = "terraform-asg-example"
      + port                               = 8080
      + preserve_client_ip                 = (known after apply)
      + protocol                           = "HTTP"
      + protocol_version                   = (known after apply)
      + proxy_protocol_v2                  = false
      + slow_start                         = 0
      + tags_all                           = (known after apply)
      + target_type                        = "instance"
      + vpc_id                             = "vpc-ddd540b6"

      + health_check {
          + enabled             = true
          + healthy_threshold   = 2
          + interval            = 15
          + matcher             = "200"
          + path                = "/"
          + port                = "traffic-port"
          + protocol            = "HTTP"
          + timeout             = 3
          + unhealthy_threshold = 2
        }

      + stickiness {
          + cookie_duration = (known after apply)
          + cookie_name     = (known after apply)
          + enabled         = (known after apply)
          + type            = (known after apply)
        }
    }

  # aws_security_group.alb will be created
  + resource "aws_security_group" "alb" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 0
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "-1"
              + security_groups  = []
              + self             = false
              + to_port          = 0
            },
        ]
      + id                     = (known after apply)
      + ingress                = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 80
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 80
            },
        ]
      + name                   = "terraform-example-alb"
      + name_prefix            = (known after apply)
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + tags_all               = (known after apply)
      + vpc_id                 = (known after apply)
    }

Plan: 7 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + alb_dns_name = (known after apply)

terraform apply 명령어를 입력 후 yes를 입력 하면 실제 AWS 인프라에 적용이 된다.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_launch_configuration.example: Creating...
aws_lb_target_group.asg: Creating...
aws_security_group.alb: Creating...
.
.
.
내용생략
.
.
.
Apply complete! Resources: 7 added, 0 changed, 0 destroyed.

Outputs:

alb_dns_name = "terraform-asg-example-1237943172.ap-northeast-2.elb.amazonaws.com"

위의 내용에서 alb_dns_name은 우리가 위의 terraform 생성 코드에서 output 값으로 지정한 값이다.

그렇다면 정상적으로 생성이 되었는지 AWS 콘솔에서 확인해 본다.

AutoScalingGroup 확인

ASG가 정상적으로 생성이 되었는지 확인해본다.

EC2 인스턴스 확인

EC2 인스턴스가 정상적으로 생성 되었는지 확인

로드 밸런서 확인

로드 밸런서 탭으로 이동하여 로드 밸런서를 확인해 보면 DNS 주소를 정상적으로 받아온 것을 확인 할 수 있다.

대상그룹 확인

대상 그룹도 정상적으로 생성된 것을 확인 할 수 있다.

마지막으로 alb_dns_name 값을 기반으로 URL을 호출하여 정상적으로 처리되는지 확인

CMD에서 정상적으로 값을 호출함

웹 브라우저

정상적으로 호출확인

리소스 정리 (삭제)

지금까지 생성했던 모든 리소스 삭제

아래의 명령어를 terraform 파일이 있는 경로에서 실행

terraform destroy

명령어를 입력하면 아래와 같이 삭제 리소스와 리스트가 출력된다.

C:\Users\hist\terraform>terraform destroy
aws_security_group.alb: Refreshing state... [id=sg-0442b05e046981a31]
aws_security_group.instance: Refreshing state... [id=sg-00c6a7f6821e544df]
aws_launch_configuration.example: Refreshing state... [id=terraform-20220414032006434100000001]
aws_instance.example: Refreshing state... [id=i-0edbcd49d135265aa]
aws_lb_target_group.asg: Refreshing state... [id=arn:aws:elasticloadbalancing:ap-northeast-2:123456789011:targetgroup/terraform-asg-example/f6c0e3db1503e975]
aws_lb.example: Refreshing state... [id=arn:aws:elasticloadbalancing:ap-northeast-2:123456789011:loadbalancer/app/terraform-asg-example/41e431db66a4689c]
aws_autoscaling_group.example: Refreshing state... [id=terraform-20220414032007118300000002]
aws_lb_listener.http: Refreshing state... [id=arn:aws:elasticloadbalancing:ap-northeast-2:123456789011:listener/app/terraform-asg-example/41e431db66a4689c/085ba85361b649a4]
aws_lb_listener_rule.asg: Refreshing state... [id=arn:aws:elasticloadbalancing:ap-northeast-2:123456789011:listener-rule/app/terraform-asg-example/41e431db66a4689c/085ba85361b649a4/63105db272a311ba]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_autoscaling_group.example will be destroyed
  - resource "aws_autoscaling_group" "example" {
      - arn                       = "arn:aws:autoscaling:ap-northeast-2:123456789011:autoScalingGroup:89016923-4c19-4e8c-9e31-d67012658054:autoScalingGroupName/terraform-20220414032007118300000002" -> null
      - availability_zones        = [
          - "ap-northeast-2a",
          - "ap-northeast-2c",
          - "ap-northeast-2d",
        ] -> null
      - capacity_rebalance        = false -> null
      - default_cooldown          = 300 -> null
      - desired_capacity          = 1 -> null
      - enabled_metrics           = [] -> null
      - force_delete              = false -> null
      - force_delete_warm_pool    = false -> null
      - health_check_grace_period = 300 -> null
      - health_check_type         = "ELB" -> null
      - id                        = "terraform-20220414032007118300000002" -> null
      - launch_configuration      = "terraform-20220414032006434100000001" -> null
      - load_balancers            = [] -> null
      - max_instance_lifetime     = 0 -> null
      - max_size                  = 3 -> null
      - metrics_granularity       = "1Minute" -> null
      - min_size                  = 1 -> null
      - name                      = "terraform-20220414032007118300000002" -> null
      - name_prefix               = "terraform-" -> null
      - protect_from_scale_in     = false -> null
      - service_linked_role_arn   = "arn:aws:iam::123456789011:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling" -> null
      - suspended_processes       = [] -> null
      - target_group_arns         = [
          - "arn:aws:elasticloadbalancing:ap-northeast-2:123456789011:targetgroup/terraform-asg-example/f6c0e3db1503e975",
        ] -> null
      - termination_policies      = [] -> null
      - vpc_zone_identifier       = [
          - "subnet-6ce20923",
          - "subnet-9b8403f0",
          - "subnet-e45194bb",
        ] -> null
      - wait_for_capacity_timeout = "10m" -> null

      - tag {
          - key                 = "Name" -> null
          - propagate_at_launch = true -> null
          - value               = "terraform-asg-example" -> null
        }
    }
    .
    .
    .
    이하생략

마지막에 정말로 해당 리소스를 삭제할 것인지 물어보는 물음창이 나온다. 물음창에 yes를 입력하면 삭제가 진행된다.

아래의 내용은 삭제가 진행되고 있는 화면이다.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_lb_listener_rule.asg: Destroying... [id=arn:aws:elasticloadbalancing:ap-northeast-2:123456789011:listener-rule/app/terraform-asg-example/41e431db66a4689c/085ba85361b649a4/63105db272a311ba]
aws_autoscaling_group.example: Destroying... [id=terraform-20220414032007118300000002]
aws_instance.example: Destroying... [id=i-0edbcd49d135265aa]
aws_lb_listener_rule.asg: Destruction complete after 0s
aws_lb_listener.http: Destroying... [id=arn:aws:elasticloadbalancing:ap-northeast-2:123456789011:listener/app/terraform-asg-example/41e431db66a4689c/085ba85361b649a4]
aws_lb_listener.http: Destruction complete after 0s
.
.
.
Destroy complete! Resources: 9 destroyed.

최종적으로 9개의 리소스가 삭제 되었다고 나온다.

이로써 정상적으로 자원의 삭제가 완료 되었다.