Terraform으로 Application Load Balancer를 만들면 리소스가 하나로 끝나지 않는다. aws_lb, aws_lb_listener, aws_lb_listener_rule, aws_lb_target_group 네 개가 서로의 arn을 주고받으며 이어진다. 코드만 보면 누가 누구를 참조하는지는 보여도 요청이 어떤 순서로 흘러가는지는 잘 안 보인다.
그중 타겟 그룹은 리스너 규칙과 Auto Scaling Group(ASG), 두 곳에서 참조된다. 이 글에서는 요청이 들어오는 순서대로 네 리소스를 따라가고, 타겟 그룹이 ASG에서도 쓰이는 이유를 헬스체크 쪽에서 정리한다.
flowchart LR
C["클라이언트 요청"] --> LB["aws_lb<br>타입·서브넷·보안그룹"]
LB --> L["aws_lb_listener<br>포트·프로토콜, default action"]
L --> R["aws_lb_listener_rule<br>condition → action"]
R -->|forward| TG["aws_lb_target_group<br>헬스체크 명세"]
ASG["aws_autoscaling_group"] -.->|EC2를 등록| TG요청은 로드밸런서, 리스너, 리스너 규칙 순으로 내려간다
로드밸런서
로드밸런서 리소스에서는 세 가지를 정한다. 로드밸런서 타입(Network 또는 Application), 어느 서브넷에 둘지, 어떤 보안그룹을 붙일지다.
resource "aws_lb" "alb" {
name_prefix = var.alb_name
load_balancer_type = "application"
subnets = data.aws_subnets.default.ids
security_groups = [aws_security_group.alb.id]
}
리스너
리스너는 특정 로드밸런서를 참조하고, 들어온 요청의 포트와 프로토콜을 확인한다. default_action은 리스너 규칙 어디에도 걸리지 않는 요청이 들어왔을 때 돌려줄 응답이다.
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.alb.arn
port = 80
protocol = "HTTP"
# 규칙에 걸리지 않는 요청에는 기본으로 단순한 404 페이지를 돌려준다
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404: page not found\n"
status_code = 404
}
}
}
리스너 규칙
리스너 규칙도 특정 리스너를 참조한다. 요청이 어떤 경로로 들어왔는지 패턴을 보고, 그 경로의 요청을 어느 타겟 그룹으로 보낼지 정한다. 리스너와 타겟 그룹을 실제로 이어 주는 건 리스너 규칙이다.
resource "aws_lb_listener_rule" "asg" {
listener_arn = aws_lb_listener.http.arn
priority = 100
condition {
path_pattern {
values = ["*"]
}
}
# action: 요청이 condition을 만족할 때 수행할 동작
action {
type = "forward"
target_group_arn = aws_lb_target_group.asg.arn
}
}
이 예제는 path_pattern이 *라서 모든 경로가 규칙에 걸린다. 리스너의 404 default_action은 규칙을 경로별로 나눠야 비로소 쓰임새가 생긴다.
경로마다 다른 타겟 그룹으로 보내려면 규칙을 나눈다
/static1/로 시작하는 요청은 대상 그룹 A로, /static2/로 시작하는 요청은 대상 그룹 B로 보내고 싶다고 하자. aws_lb_listener_rule 하나에 두 경로를 몰아넣는 게 아니라, 리스너 규칙 2개와 대상 그룹 2개를 만든다.
# 대상 그룹 A 설정
resource "aws_lb_target_group" "group_a" {
// 대상 그룹 A에 대한 설정...
}
# 대상 그룹 B 설정
resource "aws_lb_target_group" "group_b" {
// 대상 그룹 B에 대한 설정...
}
# "/static1/" 경로로 시작하는 요청을 대상 그룹 A로 라우팅
resource "aws_lb_listener_rule" "rule_static1" {
listener_arn = aws_lb_listener.http.arn
priority = 100
action {
type = "forward"
target_group_arn = aws_lb_target_group.group_a.arn
}
condition {
path_pattern {
values = ["/static1/*"]
}
}
}
# "/static2/" 경로로 시작하는 요청을 대상 그룹 B로 라우팅
resource "aws_lb_listener_rule" "rule_static2" {
listener_arn = aws_lb_listener.http.arn
priority = 110
action {
type = "forward"
target_group_arn = aws_lb_target_group.group_b.arn
}
condition {
path_pattern {
values = ["/static2/*"]
}
}
}
이렇게 나누면 /static1/도 /static2/도 아닌 요청은 어느 규칙에도 걸리지 않고, 앞에서 정한 404 default_action으로 떨어진다.
타겟 그룹은 헬스체크 명세서다
헬스체크를 실제로 하는 주체는 로드밸런서다. 타겟 그룹은 인스턴스 집합을 어떤 방식으로 헬스체크할지 적어 둔 명세서에 가깝다. 어느 경로로, 어떤 응답 코드를 정상으로 볼지, 몇 초 간격으로 몇 번 연속 실패하면 비정상으로 볼지가 여기 들어간다.
resource "aws_lb_target_group" "asg" {
name = var.alb_name
port = 80
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
}
}
이 설정이면 15초마다 /로 HTTP 요청을 보내고, 3초 안에 200이 오지 않으면 실패로 센다. 두 번 연속 실패하면 비정상, 두 번 연속 성공하면 정상이다. 계산상 인스턴스가 죽고 나서 비정상으로 판정되기까지 30초 안팎이 걸린다.
ASG가 EC2를 새로 만들면 그 EC2는 이 타겟 그룹에 포함된 채로 생성된다. 그래서 타겟 그룹은 리스너 규칙에서도, ASG에서도 참조된다.
ASG의 헬스체크 타입을 ELB로 두는 이유
헬스체크는 로드밸런서만 하는 게 아니라 ASG에도 자체 기능이 있다. 문제는 기본값인 “EC2 타입"이 무엇을 보느냐다.
EC2 타입은 타겟 그룹의 상태를 보지 않고 인스턴스 자체의 상태로 판단한다. AWS가 EC2 인스턴스 상태를 주기적으로 확인하다가 인스턴스가 running 상태에서 벗어나면 문제가 있다고 본다. OS 수준에서 인스턴스가 살아 있는지는 알 수 있지만, 그 위의 애플리케이션이 응답하지 않는 상황은 잡아내지 못할 수 있다.
ELB 타입으로 두면 타겟 그룹 설정이 함께 필요하다. 이때 ASG는 인스턴스 상태에 더해 로드밸런서가 타겟 그룹 명세대로 수행한 헬스체크 결과까지 보고 판단한다. 애플리케이션이 죽어서 /가 200을 못 돌려주면, 인스턴스가 running이어도 ASG가 교체 대상으로 본다.
| 구분 | 로드밸런서 헬스체크 | ASG 헬스체크 (EC2 타입) | ASG 헬스체크 (ELB 타입) |
|---|---|---|---|
| 보는 것 | 타겟 그룹 명세대로 보낸 요청의 응답 | 인스턴스 상태(running 여부) | 인스턴스 상태 + 로드밸런서 헬스체크 결과 |
| 비정상일 때 하는 일 | 그 인스턴스로 요청을 보내지 않는다 | 인스턴스를 교체한다 | 인스턴스를 교체한다 |
둘 다 헬스체크를 하지만 목적이 다르다. 로드밸런서는 요청을 처리할 수 있는 서버로만 요청을 보내는 데 집중한다. ASG 쪽은 그룹 안 인스턴스 상태를 지켜보다가 필요하면 교체한다. 둘이 맞물려야 가용성과 탄력성이 함께 올라간다.
정리
요청은
aws_lb→aws_lb_listener→aws_lb_listener_rule→aws_lb_target_group순으로 내려간다. 규칙에 걸리지 않은 요청은 리스너의default_action이 받는다.경로별 분기는 규칙 하나가 아니라 규칙 여러 개와 타겟 그룹 여러 개로 만든다.
타겟 그룹은 트래픽의 목적지이면서 헬스체크 명세서다. 그래서 리스너 규칙과 ASG가 같이 참조한다.
ASG 헬스체크를 ELB 타입으로 두면 애플리케이션 수준의 장애까지 인스턴스 교체로 이어진다.
리소스 네 개를 한 번에 보면 복잡하지만, 요청 하나를 따라가면 한 줄로 이어진다.