CLOUDWAVE

IaC : Terraform 실습 - 웹클러스터 배포, 로드밸런서 배포, 자원반환

갬짱 2025. 2. 7. 22:55

 

웹서버 클러스터 배포

여러개의 인스턴스를 만들어 서버 클러스터 구축 → 개별 인스턴스에 문제 발생시(트래픽 과부하) 다른 인스턴스가 동작하며 다운타임을 줄이고 적절하게 라우팅하도록 하여 안전성을 확보가능

 

AWS의 ASG(Auto Scaling Group) 서비스 : EC2 인스턴스 클러스터 시작, 각 인스턴스 상태 모니터링, 실패한 인스턴스 교체, 로드에 따른 클러스터 크기 조정 등 많은 작업을 자동으로 처리

 

(1) 사전에 launch_configuration( launch_template ) 설정 : 각 EC2 인스턴스를 구성하는 방법을 지정 (공통속성에 대한 정의)

resource "aws_launch_configuration" "example" {
  image_id        = "ami-024ea438ab0376a47"
  instance_type   = "t3.micro"
  security_groups = [aws_security_group.instance.id]
  user_data       = <<-EOF
  #!/bin/bash
  echo "Hello, World" > index.html
  nohup busybox httpd -f -p ${var.server_port} &
  EOF
  lifecycle {
   create_before_destroy = true
  }
}

 

수명 주기 블록 : 삭제이후 만들기

lifecycle {
	create_before_destroy = true
}

Terraform의 기본동작은 이전리소스 삭제 → 대체 리소스 생성 ( 시작구성변경시 새로운 시작구성 생성전에 삭제 ) → 그러나 이를 참조하는 리소스( ASG )의 존재하여 문제가 발생 이에 따라 리소스 교체순서를 반전 : 대체 리소스를 생성후( 참조의 업데이트 포함 ) → 리소스 삭제

 

 

(2) 기존의 resource aws_instance를 삭제하고 ASG 그룹 선언( launch_config와 이를 사용하는 autoscaling_group )

⇒ 기존의 인스턴스 생성결과처럼 output public ip는 출력될 수 없음

resource "aws_autoscaling_group" "example" {
 vpc_zone_identifier = data.aws_subnets.default.ids
 launch_configuration = aws_launch_configuration.example.name
 min_size = 2
 max_size = 10
 tag {
  key = "Name"
  value = "terraform-asg-example"
  propagate_at_launch = true
 }
}
  • 2~10개의 EC2 인스턴스(초기 시작 시 기본값은 2개)에서 실행, terraform-asg-example이라는 이름지정
  • subnet_ids : ASG 인스턴스가 배포되어야하는 서브넷을 지정 여러 서브넷에 인스턴스를 배포하는 속성(vpc_zone_identifier) → 모든 서브넷 목록을 가져옴( 데이터 소스를 로드하여 이용 ) 모든 서브넷에 서버들이 분산되어 생성될 수 있음

** 데이터 소스 : Terraform을 실행할 때마다 공급자(AWS)에서 가져오는 읽기 전용 정보를 나타냄( 리소스 생성X, 존재하는 리소스 )

공급자의 API에서 데이터를 쿼리하고 해당 데이터를 나머지 Terraform 코드에서 사용할 수 있도록 하는 방법

# 데이터 소스 선언
data "<PROVIDER>_<TYPE>" "<NAME>" {
	[CONFIG ...]
}

# 데이터 소스 사용 -> 값을 이용
data.<PROVIDER>_<TYPE>.<NAME>.<ATTRIBUTE>

TYPE : 사용하려는 데이터 소스 유형

NAME : Terraform 코드 전체에서 이 데이터 소스를 참조하는 데 사용할 수 있는 식별자

data "aws_vpc" "default" {
	default = true
}

data "aws_subnets" "default" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.default.id]
  }
}
  • aws_vpc 데이터 소스에서 필요한 필터는 default = true → AWS 계정에서 기본 VPC를 조회하도록 지시
  • aws_subnets( 여러 서브넷 ) : 소속된 VPC가 default VPC인것으로 필터링 ⇒ 기본 VPC내부의 서브넷들의 모음
provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_launch_configuration" "example" {
  image_id        = "ami-024ea438ab0376a47"
  instance_type   = "t3.micro"
  security_groups = [aws_security_group.instance.id]
  user_data       = <<-EOF
  #!/bin/bash
  echo "Hello, World" > index.html
  nohup busybox httpd -f -p ${var.server_port} &
  EOF
  lifecycle {
   create_before_destroy = true
  }
}

resource "aws_autoscaling_group" "example" {
 vpc_zone_identifier = data.aws_subnets.default.ids
 launch_configuration = aws_launch_configuration.example.name
 min_size = 2
 max_size = 10
 tag {
 key = "Name"
 value = "terraform-asg-example"
 propagate_at_launch = true
 }
}

resource "aws_security_group" "instance" {
  name = "terraform-example-instance"

  ingress {
    from_port   = var.server_port
    to_port     = var.server_port
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

variable "server_port" {
  description = "The port. the server will user for HTTP requests"
  type = number
  default = 8080
}

data "aws_vpc" "default" {
  default = true
}
data "aws_subnets" "default" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.default.id]
  }
}

 

 

로드밸런서 배포

각각 고유한 IP 주소를 가진 여러 서버들은 일반적으로 최종 사용자에게 사용할 단일 IP만 제공 ⇒ 로드밸런서를 배포하여 외부적으로 사용자에게 하나의 이름( DNS )( 하나의 IP )를 제공 & 내부적으로 서버그룹에 트래픽을 분산

  • 전반적인 아키텍처 : LB만 public subnet에 위치시키고 webserver, APserver, DBserver모두 private에 위치시킴( 외부에서 들어오는 client의 요청은 public LB가 처리, vpc내에서는 자유로운 통신이 가능하기에 LB가 webserver에 요청전달 )
  • LB는 proxy처럼 동작 downstream의 Listener에서 받을 데이터 설정 & upstream에서 목적지( targetgroup ) endpoint에 대한 설정
  • AWS의 세가지 유형 로드밸런서
    • ALB : 애플리케이션 계층(계층 7)에서 작동, 도메인기반의 라우팅 & 패스기반의 라우팅 지원( HTTP 및 HTTPS 트래픽 )
      ⇒ 리스너(Listener) : 특정포트와 프로토콜( HTTP, HTTPS )를 수신
      ⇒ 리스너 규칙(Listener rules) : 경로 및 호스트 이름에 따라 특정 대상그룹으로 요청 전달
      ⇒ 대상그룹(target group) : 요청을 수신하는 하나 이상의 서버
    • NLB : 전송 계층(계층 4)에서 작동, TCP UDP TLS 트래픽, ALB보다 더 빠르게 로드에 응 답하여 확장 및 축소
    • CLB : OSI 모델의 애플리케이션 계층(L7)과 전송 계층(L4) 모두에서 작동, 레거시 모델

  • MSA구현의 필수기능 : service discovery, circuit breaking(회로차단기)

 

[ 리스너 실습 ]

 

(1) ALB타입의 로드밸런서 생성

resource "aws_lb" "example" {
	name = "terraform-asg-example"
	load_balancer_type = "application"
	subnets = data.aws_subnets.default.ids
	security_groups = [aws_security_group.alb.id]
}
  • 로드밸런서의 이름과 종류를 지정가능
  • aws_subnets 데이터 원본을 사용하여 기본 VPC의 모든 서브넷을 사용하도록 로드 밸런서를 구성 ( 모든 서브넷( public )에 LB를 구축 ) ⇒ 모든 서브넷에 걸쳐서 로드 밸런서를 생성하고 트래픽을 수용할 수 있도록 설정
  • 전용 보안그룹을 생성하고 적용

 

(2) LB전용 보안그룹

보안그룹( instance ) : 80포트에서 들어오는 요청을 허용 & health check를 위해 모든 포트에서 나가는 요청을 허용

AWS 리소스는 기본적으로 모든 트래픽을 허용하지 않음 → ALB 전용으로 새 보안 그룹을 생성, 들어오는 모든 IP의 HTTP 요청을 허용( 80포트 ), 나가는 모든 IP의 모든 요청을 허용 ( protocol = "-1” )

resource "aws_security_group" "alb" {
	name = "terraform-example-alb"
	
	# Allow inbound HTTP requests
	ingress {
		from_port = 80
		to_port = 80
		protocol = "tcp"
		cidr_blocks = ["0.0.0.0/0"]
	}

	# Allow all outbound requests
	egress {
		from_port = 0
		to_port = 0
		protocol = "-1"
		cidr_blocks = ["0.0.0.0/0"]
	}
}

 

 

(3) 로드밸런서의 리스너 설정 ( upstream쪽 설정 )

resource "aws_lb_listener" "http" {
	load_balancer_arn = aws_lb.example.arn
	port = 80
	protocol = "HTTP"
	# By default, return a simple 404 page
	default_action {
		type = "fixed-response"
		fixed_response {
			content_type = "text/plain"
			message_body = "404: page not found"
			status_code = 404
		}
	}
}

기본 HTTP 포트인 포트 80에서 수신 대기

리스너 규칙과 일치하지 않는 요청에 대한 기본 응답으로 간단한 404 페이지를 보냄

 

 

(4) 타겟그룹 설정 ( downstream쪽 설정 )

  • aws_lb_target_group 리소스를 생성하고 health check 설정
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
	}
}
  • health check구성
    • matcher로 200의 응답만 정상상태로 판단
    • healthy_threshold : 문제가 발생하여 차단후 정상화되었을때 정상적으로 판단할 수 있는 성공 임계값
    • unhealthy_threshold : 문제발생으로 취급할 수 있는 실패 임계값
  • auto scaling group을 사용하여 target group으로 지정할것 ( target_group_arns, health_check_type 설정 : 기본 상태체크 타입 EC2에서 강력한 ELB로 변경 ) 혹은 aws_lb_tar get_group_attachment 리소스를 이용하여 정적 인스턴스 목록을 연결할 수 있음

 

 

(5) 리스너 규칙(listener_rule) 생성 : 타켓그룹으로 받아서 forward로 전달

ASG가 포함된 대상 그룹에 대한 경로와 일치하는 요청을 보내는 리스너 규칙을 정의

→ 구현 생략

 

(+) output으로 alb_dns_name을 출력

output "alb_dns_name" {
 value = aws_lb.example.dns_name
 description = "The domain name of the load balancer"
}

ALB(Application Load Balancer)는 여러 서브넷에 걸쳐 배포될 수 있지만, Terraform에서는 ALB의 DNS 이름(DNS Name)이 하나로 제공됩니다. 이 값은 모든 퍼블릭 서브넷의 ALB 리소스를 대표하는 유일한 접근 지점

여러 퍼블릭 서브넷에 생성된 ALB 리소스는 내부적으로 동일한 DNS 이름으로 관리되며, AWS는 요청을 서브넷에 있는 ALB 노드들로 분산

 

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

resource "aws_lb" "example" {
  name               = "terraform-asg-example"
  load_balancer_type = "application"
  subnets            = data.aws_subnets.default.ids
  security_groups    = [aws_security_group.alb.id]
}
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.example.arn
  port              = 80
  protocol          = "HTTP"
  # By default, return a simple 404 page
  default_action {
    type = "fixed-response"
    fixed_response {
      content_type = "text/plain"
      message_body = "404: page not found"
      status_code  = 404
    }
  }
}

resource "aws_security_group" "alb" {
  name = "terraform-example-alb"
  # Allow inbound HTTP requests
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  # Allow all outbound requests
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_launch_configuration" "example" {
  image_id        = "ami-024ea438ab0376a47"
  instance_type   = "t3.micro"
  security_groups = [aws_security_group.instance.id]
  user_data       = <<-EOF
 #!/bin/bash
 echo "Hello, World" > index.html
 nohup busybox httpd -f -p ${var.server_port} &
 EOF
 lifecycle {
  create_before_destroy = true
 }
}

resource "aws_autoscaling_group" "example" {
 vpc_zone_identifier = data.aws_subnets.default.ids
 launch_configuration = aws_launch_configuration.example.name
 min_size = 2
 max_size = 10
 tag {
 key = "Name"
 value = "terraform-asg-example"
 propagate_at_launch = true
 }
}

resource "aws_security_group" "instance" {
  name = "terraform-example-instance"

  ingress {
    from_port   = var.server_port
    to_port     = var.server_port
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

variable "server_port" {
  description = "The port. the server will user for HTTP requests"
  type = number
  default = 8080
}

data "aws_vpc" "default" {
  default = true
}
data "aws_subnets" "default" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.default.id]
  }
}

terraform apply 를 실행 → LB의 DNS 이름이 출력됨

 

DNS로 접속시 하나의 서버로 정상접속이 되고 있음!

 

지금까지 생성한 리소스들

 

자원반환

Terraform은 생성한 리소스를 추적하므로 정리가 간단하다

terraform destroy