테라폼 - ALB와 Target Group 구현하기
외부 요청을 애플리케이션 서버로 안전하게 전달하기
이전 글에서는 Terraform으로 Secrets Manager와 SSM Parameter Store를 구현하는 방법을 정리했다.
이번 글에서는 AWS에서 웹 서비스를 구성할 때 자주 사용하는 ALB와 Target Group을 Terraform으로 구현해보려 한다.
ALB는 Application Load Balancer의 약자다.
사용자의 HTTP 또는 HTTPS 요청을 받아 뒤쪽의 EC2, ECS Task, Lambda, IP Target 등으로 전달하는 역할을 한다.
User
→ ALB
→ Target Group
→ EC2 / ECS / Lambda
처음에는 ALB를 단순히 “트래픽을 나눠주는 리소스” 정도로 이해할 수 있다.
하지만 실제로는 Listener, Target Group, Health Check, Security Group, 인증서, Redirect 설정까지 함께 이해해야 제대로 사용할 수 있다.
ALB는 외부 요청을 받는 입구이고, Target Group은 요청을 전달할 대상 목록이다.
목차
- 1. ALB란 무엇인가
- 2. ALB를 구성하는 주요 요소
- 3. ALB와 Target Group의 관계
- 4. Public ALB와 Internal ALB
- 5. ALB Security Group 구성
- 6. Target Group 만들기
- 7. ALB 만들기
- 8. Listener 만들기
- 9. Target Group Attachment
- 10. HTTPS Listener와 HTTP Redirect
- 11. Listener Rule로 경로 기반 라우팅하기
- 12. 실전 예제: ALB → EC2 구조
- 13. 의존성 흐름
- 14. 자주 하는 실수
- 15. 마무리
1. ALB란 무엇인가
ALB는 Application Load Balancer의 약자다.
AWS Elastic Load Balancing 서비스의 한 종류이며, HTTP와 HTTPS 요청을 애플리케이션 계층에서 처리한다.
ALB는 다음과 같은 상황에서 자주 사용된다.
여러 EC2로 트래픽 분산
ECS Fargate 서비스 앞단 구성
HTTPS 인증서 연결
HTTP 요청을 HTTPS로 Redirect
/api, /admin 같은 경로 기반 라우팅
정상 서버에만 트래픽 전달
예를 들어 사용자가 웹 서비스에 접속하면 ALB가 요청을 받고, 뒤쪽 Target Group에 등록된 정상 대상에게 트래픽을 전달한다.
User
→ ALB
→ Target Group
→ Healthy Target
여기서 중요한 것은 ALB가 단순히 요청을 전달하는 것만 하는 게 아니라는 점이다.
ALB는 Health Check를 통해 정상 대상만 골라서 트래픽을 보낼 수 있다.
정상 Target
→ 트래픽 전달
비정상 Target
→ 트래픽 제외
2. ALB를 구성하는 주요 요소
ALB를 Terraform으로 구현할 때는 다음 리소스를 함께 봐야 한다.
| 구성 요소 | Terraform 리소스 | 역할 |
|---|---|---|
| ALB | aws_lb | Load Balancer 본체 |
| Target Group | aws_lb_target_group | 트래픽을 전달할 대상 그룹 |
| Listener | aws_lb_listener | ALB가 받을 포트와 프로토콜 |
| Listener Rule | aws_lb_listener_rule | 경로, Host 조건에 따른 라우팅 |
| Target Attachment | aws_lb_target_group_attachment | EC2 같은 대상을 Target Group에 등록 |
| Security Group | aws_security_group | ALB와 App 간 네트워크 접근 제어 |
구조를 단순화하면 다음과 같다.
ALB
→ Listener
→ Target Group
→ Target
사용자는 ALB로 접속하고, ALB는 Listener 규칙을 보고 Target Group으로 요청을 보낸다.
3. ALB와 Target Group의 관계
ALB와 Target Group은 서로 역할이 다르다.
ALB
→ 요청을 받는 입구
Listener
→ 어떤 포트와 프로토콜로 받을지 결정
Target Group
→ 요청을 보낼 대상 목록
Target
→ 실제 요청을 처리하는 EC2, ECS Task, Lambda 등
예를 들어 다음 구조를 생각해보자.
User
→ ALB: 80
→ Listener: HTTP 80
→ Target Group: app-tg
→ EC2: 8080
사용자는 ALB의 80번 포트로 요청한다.
하지만 실제 애플리케이션 서버는 8080번 포트에서 동작할 수 있다.
이 경우 ALB Listener는 80번 포트에서 요청을 받고, Target Group은 EC2의 8080번 포트로 트래픽을 전달한다.
ALB가 받는 포트와 애플리케이션이 실제로 듣는 포트는 다를 수 있다.
4. Public ALB와 Internal ALB
ALB는 크게 두 가지 방식으로 만들 수 있다.
Internet-facing ALB
Internal ALB
| 구분 | 의미 | 사용 예 |
|---|---|---|
| Internet-facing | 인터넷에서 접근 가능한 ALB | 외부 웹 서비스, API 서버 |
| Internal | VPC 내부에서만 접근 가능한 ALB | 내부 API, 마이크로서비스 통신 |
외부 사용자가 접속하는 웹 서비스라면 보통 Internet-facing ALB를 사용한다.
Internet
→ Public ALB
→ Private EC2 / ECS
반대로 내부 서비스 간 통신에는 Internal ALB를 사용할 수 있다.
Service A
→ Internal ALB
→ Service B
이 글에서는 초보자가 이해하기 쉬운 Internet-facing ALB 기준으로 설명한다.
5. ALB Security Group 구성
ALB를 사용할 때 Security Group은 보통 두 개로 나누어 생각한다.
ALB Security Group
→ 외부 사용자가 ALB에 접근할 수 있게 허용
App Security Group
→ ALB에서 오는 요청만 애플리케이션 서버에 허용
예를 들어 웹 서비스 구조는 다음과 같다.
User
→ ALB Security Group: 80 또는 443 허용
→ App Security Group: ALB Security Group에서 오는 8080만 허용
Terraform 코드로 보면 먼저 ALB Security Group을 만든다.
resource "aws_security_group" "alb" {
name = "${var.project_name}-alb-sg"
description = "Security group for ALB"
vpc_id = var.vpc_id
tags = merge(local.common_tags, {
Name = "${var.project_name}-alb-sg"
})
}
resource "aws_vpc_security_group_ingress_rule" "alb_http" {
security_group_id = aws_security_group.alb.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "tcp"
from_port = 80
to_port = 80
description = "Allow HTTP from anywhere"
}
resource "aws_vpc_security_group_ingress_rule" "alb_https" {
security_group_id = aws_security_group.alb.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "tcp"
from_port = 443
to_port = 443
description = "Allow HTTPS from anywhere"
}
resource "aws_vpc_security_group_egress_rule" "alb_all" {
security_group_id = aws_security_group.alb.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "-1"
description = "Allow all outbound traffic"
}
그리고 애플리케이션 서버 Security Group에서는 ALB Security Group에서 오는 요청만 허용한다.
resource "aws_security_group" "app" {
name = "${var.project_name}-app-sg"
description = "Security group for application"
vpc_id = var.vpc_id
tags = merge(local.common_tags, {
Name = "${var.project_name}-app-sg"
})
}
resource "aws_vpc_security_group_ingress_rule" "app_from_alb" {
security_group_id = aws_security_group.app.id
referenced_security_group_id = aws_security_group.alb.id
ip_protocol = "tcp"
from_port = 8080
to_port = 8080
description = "Allow app traffic from ALB"
}
이 구조의 핵심은 EC2나 ECS를 인터넷 전체에 직접 열지 않는다는 점이다.
권장 구조:
Internet → ALB → App
피하고 싶은 구조:
Internet → App 직접 접근
6. Target Group 만들기
Target Group은 ALB가 요청을 전달할 대상 그룹이다.
Target Group에는 대상 타입이 있다.
| target_type | 의미 | 사용 예 |
|---|---|---|
| instance | EC2 Instance ID를 대상으로 등록 | EC2 기반 서비스 |
| ip | IP 주소를 대상으로 등록 | ECS Fargate, IP Target |
| lambda | Lambda 함수를 대상으로 등록 | Lambda 기반 HTTP 처리 |
EC2를 대상으로 하는 Target Group은 다음처럼 만들 수 있다.
resource "aws_lb_target_group" "app" {
name = "${var.project_name}-app-tg"
port = 8080
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "instance"
health_check {
enabled = true
path = "/health"
matcher = "200"
interval = 30
timeout = 5
healthy_threshold = 2
unhealthy_threshold = 2
}
tags = merge(local.common_tags, {
Name = "${var.project_name}-app-tg"
})
}
여기서 중요한 설정은 health_check다.
path = "/health"
matcher = "200"
ALB는 이 경로로 주기적으로 요청을 보내고, 정상 응답이 오면 해당 Target을 healthy 상태로 본다.
GET /health
→ 200 응답
→ Healthy
반대로 애플리케이션이 /health 경로를 제공하지 않거나, 200이 아닌 응답을 반환하면 Target이 unhealthy가 될 수 있다.
7. ALB 만들기
이제 ALB 본체를 만든다.
Internet-facing ALB는 보통 Public Subnet에 배치한다.
ALB는 최소 두 개 이상의 서로 다른 AZ Subnet을 사용하는 것이 일반적이며, AWS에서도 Application Load Balancer에는 최소 두 개 이상의 AZ Subnet을 요구한다.
Public Subnet A
Public Subnet C
→ ALB
Terraform 코드는 다음과 같다.
resource "aws_lb" "app" {
name = "${var.project_name}-alb"
load_balancer_type = "application"
internal = false
security_groups = [
aws_security_group.alb.id
]
subnets = var.public_subnet_ids
enable_deletion_protection = false
tags = merge(local.common_tags, {
Name = "${var.project_name}-alb"
})
}
주요 설정은 다음과 같다.
| 설정 | 의미 |
|---|---|
| load_balancer_type | application이면 ALB 생성 |
| internal | false면 Internet-facing, true면 Internal |
| security_groups | ALB에 연결할 Security Group |
| subnets | ALB가 배치될 Subnet 목록 |
| enable_deletion_protection | ALB 삭제 보호 여부 |
학습용에서는 enable_deletion_protection = false로 둘 수 있다.
운영 환경에서는 실수로 ALB가 삭제되지 않도록 삭제 보호를 켜는 것도 고려할 수 있다.
8. Listener 만들기
Listener는 ALB가 어떤 포트와 프로토콜로 요청을 받을지 정의한다.
가장 기본적인 HTTP Listener는 다음과 같다.
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.app.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
이 코드는 다음 의미다.
ALB의 80번 포트로 들어온 HTTP 요청을
app Target Group으로 전달한다.
여기서 Listener는 ALB와 Target Group을 모두 참조한다.
aws_lb.app
→ aws_lb_listener.http
aws_lb_target_group.app
→ aws_lb_listener.http
9. Target Group Attachment
Target Group을 만들었다고 해서 EC2가 자동으로 등록되는 것은 아니다.
EC2를 Target Group에 등록해야 한다.
EC2 기반 구조에서는 aws_lb_target_group_attachment를 사용할 수 있다.
resource "aws_lb_target_group_attachment" "app" {
target_group_arn = aws_lb_target_group.app.arn
target_id = aws_instance.app.id
port = 8080
}
여기서 중요한 설정은 다음과 같다.
target_group_arn
→ 등록할 Target Group
target_id
→ 등록할 EC2 Instance ID
port
→ Target이 실제로 요청을 받을 포트
만약 애플리케이션이 EC2 내부에서 8080번 포트로 실행 중이라면 Target Group Attachment의 port도 8080으로 맞춰야 한다.
ALB Listener: 80
Target Group: 8080
EC2 App: 8080
ECS Fargate에서는 보통 Target Group Attachment를 직접 작성하지 않는다.
ECS Service의 load_balancer 블록에서 Target Group을 연결하면 ECS가 Task를 Target Group에 등록한다.
10. HTTPS Listener와 HTTP Redirect
운영 환경에서는 HTTP보다 HTTPS를 사용하는 것이 일반적이다.
ALB에서 HTTPS를 사용하려면 ACM 인증서가 필요하다.
ACM Certificate
→ ALB HTTPS Listener
→ Target Group
HTTPS Listener는 다음처럼 만들 수 있다.
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.app.arn
port = 443
protocol = "HTTPS"
certificate_arn = var.acm_certificate_arn
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
그리고 HTTP 요청은 HTTPS로 Redirect할 수 있다.
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.app.arn
port = 80
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
이렇게 하면 사용자가 HTTP로 접속해도 HTTPS로 이동한다.
http://example.com
→ https://example.com
학습용 예제에서는 HTTP Listener만으로 시작해도 된다.
하지만 실제 서비스에서는 ACM 인증서와 HTTPS Listener를 함께 구성하는 것이 좋다.
11. Listener Rule로 경로 기반 라우팅하기
ALB는 경로 기반 라우팅을 지원한다.
예를 들어 요청 경로에 따라 서로 다른 Target Group으로 보낼 수 있다.
/api/*
→ api Target Group
/admin/*
→ admin Target Group
Terraform 예시는 다음과 같다.
resource "aws_lb_target_group" "admin" {
name = "${var.project_name}-admin-tg"
port = 8081
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "instance"
health_check {
path = "/health"
matcher = "200"
}
}
resource "aws_lb_listener_rule" "admin" {
listener_arn = aws_lb_listener.https.arn
priority = 100
condition {
path_pattern {
values = ["/admin/*"]
}
}
action {
type = "forward"
target_group_arn = aws_lb_target_group.admin.arn
}
}
이 구조를 사용하면 하나의 ALB로 여러 애플리케이션을 라우팅할 수 있다.
example.com/api/*
→ API 서버
example.com/admin/*
→ 관리자 서버
다만 초보 단계에서는 먼저 기본 Listener와 하나의 Target Group 구조를 이해한 뒤, 이후 Listener Rule을 확장하는 것이 좋다.
12. 실전 예제: ALB → EC2 구조
이제 ALB에서 EC2로 트래픽을 전달하는 기본 구조를 하나로 정리해보자.
Internet
→ ALB
→ Target Group
→ EC2
12.1 variables.tf
variable "project_name" {
description = "Project name"
type = string
}
variable "vpc_id" {
description = "VPC ID"
type = string
}
variable "public_subnet_ids" {
description = "Public subnet IDs for ALB"
type = list(string)
}
variable "acm_certificate_arn" {
description = "ACM certificate ARN for HTTPS listener"
type = string
default = null
}
12.2 locals.tf
locals {
common_tags = {
Project = var.project_name
ManagedBy = "terraform"
}
}
12.3 main.tf
resource "aws_security_group" "alb" {
name = "${var.project_name}-alb-sg"
description = "Security group for ALB"
vpc_id = var.vpc_id
tags = merge(local.common_tags, {
Name = "${var.project_name}-alb-sg"
})
}
resource "aws_vpc_security_group_ingress_rule" "alb_http" {
security_group_id = aws_security_group.alb.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "tcp"
from_port = 80
to_port = 80
}
resource "aws_vpc_security_group_egress_rule" "alb_all" {
security_group_id = aws_security_group.alb.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "-1"
}
resource "aws_lb" "app" {
name = "${var.project_name}-alb"
load_balancer_type = "application"
internal = false
security_groups = [
aws_security_group.alb.id
]
subnets = var.public_subnet_ids
enable_deletion_protection = false
tags = merge(local.common_tags, {
Name = "${var.project_name}-alb"
})
}
resource "aws_lb_target_group" "app" {
name = "${var.project_name}-app-tg"
port = 8080
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "instance"
health_check {
enabled = true
path = "/health"
matcher = "200"
interval = 30
timeout = 5
healthy_threshold = 2
unhealthy_threshold = 2
}
tags = merge(local.common_tags, {
Name = "${var.project_name}-app-tg"
})
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.app.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
resource "aws_lb_target_group_attachment" "app" {
target_group_arn = aws_lb_target_group.app.arn
target_id = aws_instance.app.id
port = 8080
}
이 예제는 HTTP 기반의 가장 단순한 ALB → EC2 구조다.
실제 운영에서는 HTTPS Listener, ACM 인증서, HTTP to HTTPS Redirect를 추가하는 것이 좋다.
12.4 outputs.tf
output "alb_dns_name" {
description = "ALB DNS name"
value = aws_lb.app.dns_name
}
output "alb_arn" {
description = "ALB ARN"
value = aws_lb.app.arn
}
output "target_group_arn" {
description = "Target Group ARN"
value = aws_lb_target_group.app.arn
}
ALB 생성 후 브라우저에서 다음 주소로 접속할 수 있다.
http://ALB_DNS_NAME
다만 Target Group Health Check가 통과해야 정상적으로 응답을 받을 수 있다.
13. 의존성 흐름
ALB와 Target Group을 Terraform으로 구현할 때의 의존성 흐름은 다음과 같다.
Subnet / Security Group
→ ALB
→ Listener
→ Target Group
→ Target Attachment

이 구조에서 중요한 점은 다음과 같다.
ALB는 Subnet과 Security Group을 참조한다.
Listener는 ALB와 Target Group을 참조한다.
Target Attachment는 Target Group과 EC2를 참조한다.
ALB는 요청을 받는 리소스이고, Target Group은 요청을 전달할 대상을 관리하는 리소스다.
14. 자주 하는 실수
14.1 ALB를 하나의 Subnet에만 배치하려고 함
Application Load Balancer는 최소 두 개 이상의 서로 다른 AZ Subnet이 필요하다.
따라서 Public Subnet을 하나만 만든 상태에서 ALB를 생성하려고 하면 오류가 발생할 수 있다.
권장:
public-subnet-a
public-subnet-c
ALB subnets = [subnet-a, subnet-c]
14.2 ALB Security Group만 열고 App Security Group을 열지 않음
사용자가 ALB에 접근할 수 있어도 ALB가 애플리케이션 서버에 접근하지 못하면 서비스가 동작하지 않는다.
App Security Group에서 ALB Security Group을 source로 허용해야 한다.
ALB Security Group
→ App Security Group 8080 허용
14.3 Health Check 경로가 실제 애플리케이션에 없음
Target Group Health Check 경로가 /health인데 애플리케이션이 해당 경로를 제공하지 않으면 Target이 unhealthy가 된다.
Target Group Health Check
→ /health
Application
→ /health 없음
결과
→ Unhealthy
Spring Boot를 사용한다면 Actuator의 /actuator/health를 사용할 수도 있다.
이 경우 Target Group의 health_check path도 그에 맞게 바꿔야 한다.
path = "/actuator/health"
14.4 Target Group port와 애플리케이션 port가 다름
애플리케이션이 8080에서 실행 중인데 Target Group을 80으로 설정하면 Health Check나 요청 전달이 실패할 수 있다.
Application port: 8080
Target Group port: 80
결과:
연결 실패 가능
애플리케이션이 실제로 listen 중인 포트와 Target Group 설정을 맞춰야 한다.
14.5 HTTPS Listener에 ACM 인증서를 연결하지 않음
HTTPS Listener를 만들려면 인증서가 필요하다.
ALB에서는 보통 ACM 인증서를 사용한다.
HTTPS Listener
→ certificate_arn 필요
도메인과 인증서 구성은 이후 CloudFront, ACM, Route53 글에서 더 자세히 다루는 것이 좋다.
14.6 ECS Fargate에서 target_type을 instance로 둠
ECS Fargate는 일반적으로 target_type = "ip"를 사용한다.
EC2 기반
→ target_type = "instance"
ECS Fargate
→ target_type = "ip"
ECS 글에서 Fargate와 Target Group 연결을 다시 다룰 예정이다.
14.7 ALB 비용을 잊음
ALB는 생성해두면 비용이 발생한다.
학습용으로 만들었다면 실습 후 삭제하는 것이 좋다.
terraform destroy
단, ALB 삭제 보호를 켜두었다면 삭제 전에 해당 설정을 해제해야 한다.
15. 마무리
이번 글에서는 Terraform으로 ALB와 Target Group을 구현하는 방법을 정리했다.
ALB를 이해할 때 중요한 요소는 다음과 같다.
ALB
→ 외부 요청을 받는 입구
Listener
→ 어떤 포트와 프로토콜로 요청을 받을지 정의
Target Group
→ 요청을 전달할 대상 목록
Health Check
→ 정상 대상인지 확인
Target Attachment
→ EC2 같은 대상을 Target Group에 등록
Security Group
→ ALB와 App 사이의 접근 제어
ALB는 단순히 트래픽을 나눠주는 리소스가 아니다.
Health Check를 통해 정상 대상에게만 트래픽을 보내고, Listener Rule을 통해 요청 경로별로 다른 Target Group에 라우팅할 수 있다.
또한 운영 환경에서는 HTTPS Listener와 HTTP to HTTPS Redirect, ACM 인증서 구성을 함께 고려해야 한다.
한 줄 정리
ALB는 요청을 받는 입구이고, Target Group은 요청을 보낼 대상 목록이며, Health Check는 정상 대상만 선택하기 위한 기준이다.
다음 글에서는 ECR을 Terraform으로 구현해본다. ECR은 Docker 이미지를 저장하는 AWS 컨테이너 이미지 레지스트리이며, ECS 배포 흐름에서 중요한 기반 리소스다.
'테라폼' 카테고리의 다른 글
| 4-11. 테라폼 - ECS Fargate 기본 구현하기 (0) | 2026.05.14 |
|---|---|
| 4-10. 테라폼 - ECR 구현하기 (0) | 2026.05.14 |
| 4-8. 테라폼 - Secrets Manager와 SSM Parameter Store 구현하기 (0) | 2026.05.13 |
| 4-7. 테라폼 - RDS MySQL 구현하기 (0) | 2026.05.13 |
| 4-6. 테라폼 - S3 구현하기 (0) | 2026.05.13 |