AWS 중급 #6 ALB / NLB와 ACM (HTTPS)
#5 Route 53의 도메인이 가리키는 곳에는 거의 항상 로드 밸런서가 있습니다. AWS의 매니지드 로드 밸런서를 통틀어 ELB (Elastic Load Balancing) 라고 부르고, 그 안에 ALB / NLB / GWLB 세 가지 종류가 있습니다.
이 글에선 세 LB의 차이 → ALB의 Listener / Target Group / Health Check 흐름 → ACM으로 인증서를 받아 HTTPS를 다는 과정까지 한 줄로 정리합니다.
로드 밸런서가 푸는 문제 #
EC2 한 대 뒤에 도메인을 직접 꽂으면 다음이 깨집니다.
| 문제 | LB가 푸는 기능 |
|---|---|
| EC2가 죽으면 사이트가 다운 | Health check로 죽은 인스턴스를 빼고 살아있는 쪽으로 |
| 트래픽 많아져 한 대로 부족 | 여러 인스턴스에 부하 분산 |
| AZ 장애 시 전면 다운 | Multi-AZ 분산 |
| HTTPS 인증서 인스턴스마다 관리 | LB에서 한 번 끊어줌 (TLS termination) |
| 카나리 / Blue-Green 배포 | Listener Rule로 비율 분산 |
운영에서는 거의 항상 Internet → ALB / NLB → EC2 / ECS / Lambda 패턴입니다.
ALB / NLB / GWLB: 세 가지 비교 #
| ALB (Application LB) | NLB (Network LB) | GWLB (Gateway LB) | |
|---|---|---|---|
| OSI 레이어 | L7 (HTTP / HTTPS) | L4 (TCP / UDP / TLS) | L3/L4 (IP) |
| 라우팅 방식 | path / host / header / 메소드 | port만 | 패킷 그대로 |
| 처리량 | 좋음 | 매우 빠름 (수백만 RPS) | 적합한 경우에 빠름 |
| WebSocket | ✅ | ✅ | - |
| HTTP/2 | ✅ | ✅ (TLS) | - |
| 고정 IP | ❌ (DNS) | ✅ (AZ마다 EIP) | - |
| WAF 연동 | ✅ | ❌ | - |
| Cognito 인증 | ✅ | ❌ | - |
| Lambda target | ✅ | ❌ | - |
| 용도 | 웹 / API | 게임 / IoT / TCP / gRPC | 보안 어플라이언스 (Firewall) |
결정 가이드 #
HTTP(S) ?
├── YES → ALB
│ └── 매우 높은 RPS (수십만+)? → ALB → 한계 시 NLB
└── NO →
TCP/UDP ?
├── YES → NLB
└── 외부 보안 어플라이언스 통과 → GWLB99%의 일반 웹 워크로드는 ALB입니다. NLB는 게임 / IoT / 매우 높은 처리량 / 고정 IP가 필요한 경우에 씁니다.
ALB의 구조 #
Route 53
│
▼
┌────────┐
│ ALB │
│ │
└─┬──┬───┘
│ │
Listener (port 443)
│ │
▼ ▼
Listener Rules
(path / host)
│
├── /api/* ────▶ Target Group A (api EC2 들)
├── /admin/* ────▶ Target Group B (admin)
└── 기본 / ────▶ Target Group C (web)
│
├── EC2 #1 (AZ a)
├── EC2 #2 (AZ b)
└── EC2 #3 (AZ a)핵심 구성 요소:
1) Listener: 받는 포트 #
ALB가 어느 포트로 트래픽을 받을지. 보통 80 (HTTP), 443 (HTTPS).
aws elbv2 create-load-balancer \
--name my-alb \
--subnets subnet-pubA subnet-pubB \
--security-groups sg-alb \
--scheme internet-facing \
--type application
aws elbv2 create-listener \
--load-balancer-arn <alb-arn> \
--protocol HTTPS \
--port 443 \
--certificates CertificateArn=<acm-arn> \
--default-actions Type=forward,TargetGroupArn=<tg-arn>2) Target Group: 보낼 대상 #
Listener가 트래픽을 보낼 인스턴스 / IP / Lambda의 묶음. 포트와 프로토콜을 가진 그룹.
aws elbv2 create-target-group \
--name my-app-tg \
--protocol HTTP \
--port 8080 \
--vpc-id vpc-... \
--target-type instance \
--health-check-protocol HTTP \
--health-check-path /health \
--health-check-interval-seconds 30 \
--healthy-threshold-count 2 \
--unhealthy-threshold-count 3target-type의 의미:
| 타입 | 설명 |
|---|---|
instance | EC2 인스턴스 ID. SG 지정에 매끄러움 |
ip | 임의 IP (VPC 안). ECS / Fargate의 자동 등록 |
lambda | Lambda 함수. ALB → Lambda 패턴 |
3) Listener Rule: 라우팅 규칙 #
같은 Listener 안에서 path / host / header 조건에 따라 다른 Target Group으로 보냅니다.
Priority Condition Action
10 host = api.example.com forward → tg-api
20 path = /admin/* forward → tg-admin
30 path = /static/* redirect → cloudfront.example.com
default * forward → tg-web규칙은 priority 순 (작은 게 먼저). 매치되면 멈춤.
Listener Rule 액션 #
- forward. Target Group으로 (가중치로 여러 대상)
- redirect. 다른 URL로 (HTTP → HTTPS 영구 리다이렉트의 정석)
- fixed-response. 고정 응답 (메인터넌스 페이지)
- authenticate-cognito / -oidc. 사용자 인증 후 통과
Listener (port 80)
default action: redirect → HTTPS://#{host}#{path}#{query} (301)
Listener (port 443)
default action: forward → tg-web이 패턴이 운영의 표준. 모든 80 트래픽을 443으로 영구 보냅니다.
Health Check: 살아있는 대상만 #
Target Group의 Health Check가 죽은 인스턴스를 자동으로 빼고 살아나면 다시 넣어줍니다.
ALB ─ HTTP GET /health ──▶ EC2:8080
│
응답 200 ──▶ healthy (3회 연속)
응답 5xx ──▶ unhealthy (3회 연속) → 라우팅에서 제외자주 쓰는 옵션:
| 옵션 | 설명 |
|---|---|
HealthCheckPath | /health, /healthz 같은 가벼운 경로 |
HealthCheckProtocol | HTTP / HTTPS |
HealthCheckIntervalSeconds | 30 (보통) |
HealthyThresholdCount | 2~3회 연속 200 → healthy |
UnhealthyThresholdCount | 2~3회 연속 실패 → unhealthy |
Matcher.HttpCode | 200 또는 200-299 |
Health Check 경로 설계 #
좋은 /health 응답:
@app.get("/health")
def health():
return {"status": "ok"}DB 체크까지 하는 deep health는 별도 경로에:
@app.get("/health/deep")
def deep_health(db: Session = Depends(get_db)):
db.execute("SELECT 1")
return {"status": "ok", "db": "ok"}/health는 항상 가볍게, /health/deep은 모니터링 / 디버깅 용도로 둡니다. ALB가 deep health를 폴링하면 DB 부담이 큽니다.
Sticky Session: 같은 대상으로 #
특정 사용자의 요청을 항상 같은 인스턴스로 보내는 옵션.
aws elbv2 modify-target-group-attributes \
--target-group-arn <tg-arn> \
--attributes \
Key=stickiness.enabled,Value=true \
Key=stickiness.type,Value=lb_cookie \
Key=stickiness.lb_cookie.duration_seconds,Value=86400구성:
- 세션을 메모리에 두는 옛 앱. 거의 안 만들어야 하지만 마이그레이션 과정에만 임시로
- WebSocket 같은 장수명 연결. 자동 stickiness
운영에서는 세션을 외부 (Redis / DB)로 빼고 stateless로 두는 것이 정석입니다. Stickiness는 예외적인 경우에만 씁니다.
NLB의 용도 #
ALB가 못 푸는 경우에 NLB를 씁니다.
NLB의 강점 #
- 고정 IP (AZ마다 EIP). 방화벽 화이트리스트 용도
- 수백만 RPS 처리
- TLS termination도 가능 (NLB가 TLS 풀고 평문으로 backend)
- PrivateLink 지원 (서비스를 다른 VPC에 노출)
NLB의 약점 #
- L4만. path / host / header 라우팅 안 됨
- WAF 연동 직접 안 됨
- 단일 Listener가 한 Target Group
aws elbv2 create-load-balancer \
--name my-nlb --type network \
--subnets subnet-pubA subnet-pubB
aws elbv2 create-listener \
--load-balancer-arn <nlb-arn> \
--protocol TLS --port 443 \
--certificates CertificateArn=<acm-arn> \
--default-actions Type=forward,TargetGroupArn=<tg-arn>ACM: 인증서 발급의 용도 #
**ACM (AWS Certificate Manager)**는 AWS 안의 ALB / NLB / CloudFront / API Gateway에서 쓰는 공개 SSL 인증서를 무료로 발급하고 자동 갱신해 줍니다.
인증서 요청 #
aws acm request-certificate \
--domain-name example.com \
--subject-alternative-names "*.example.com" "api.example.com" \
--validation-method DNS \
--region ap-northeast-2검증 방식:
- DNS 검증 (권장). Route 53의 CNAME으로 자동 검증, 자동 갱신
- Email 검증 (옛 방식). admin@example.com 등으로 메일 발송, 매년 수동
거의 항상 DNS 검증. Route 53을 같이 쓰면 콘솔이 “Create record in Route 53” 버튼으로 자동.
인증서의 규칙: 리전 일치 규칙 #
ACM 인증서는 리전 단위. ALB가 서울에 있으면 인증서도 서울에서 발급. 단, CloudFront는 항상 us-east-1 (#7).
ALB (서울) → ACM 인증서 (ap-northeast-2)
NLB (도쿄) → ACM 인증서 (ap-northeast-1)
CloudFront → ACM 인증서 (us-east-1) ← 항상
API Gateway (REST) → ACM 인증서 (해당 리전)
API Gateway (Edge) → ACM 인증서 (us-east-1)자동 갱신 #
ACM 인증서는 기본 13개월 유효, 만료 60일 전부터 자동 갱신:
- DNS 검증으로 만든 인증서: 완전 자동
- Email 검증: 수동 (그래서 DNS 권장)
자동 갱신이 실패하는 경우:
- DNS 검증 CNAME이 사라짐 → 도메인 풀이 실패
- ALB가 인증서 사용 중이지만 도메인이 다른 곳으로 이동 → 검증 실패
ACM 콘솔에서 만료 알림 자동, CloudWatch 알람도 가능.
HTTPS 다는 한 줄 절차 #
도메인이 있고 ALB가 있다고 가정.
1. ACM에서 인증서 요청 (DNS 검증)
2. Route 53에 검증 CNAME 추가 (콘솔 한 번 클릭)
3. 인증서 ISSUED 대기 (수 분)
4. ALB Listener 443에 인증서 attach
5. Listener 80 → HTTPS redirect
6. Route 53의 도메인 → ALB Alias이 6단계가 운영의 표준 패턴이며, 인증서를 매년 수동 갱신할 일도 없습니다.
Security Policy: TLS 버전 #
Listener의 Security Policy가 허용할 TLS 버전과 암호 모음을 정의합니다.
ELBSecurityPolicy-TLS13-1-2-2021-06 ← 권장 (TLS 1.3, 1.2)
ELBSecurityPolicy-TLS-1-2-2017-01 ← 호환성 좋음
ELBSecurityPolicy-FS-2018-06 ← Forward Secrecy 강제옛 클라이언트 (TLS 1.0, 1.1)를 끊고 싶다면 TLS13-1-2-2021-06. 매년 새 정책이 나오니 정기 점검.
Connection Draining: 우아한 종료 #
인스턴스를 수동으로 빼거나 ASG가 줄일 때 진행 중인 요청이 끊기지 않도록 기다려 주는 기능.
ALB ─ 새 요청은 안 보냄 ─▶ EC2 (deregistration_delay = 300s)
↑
진행 중인 요청은 끝까지 처리기본 300초. 보통 30~60초로 줄여도 충분. 너무 짧으면 진행 중 요청 끊김, 너무 길면 배포가 느림.
LB의 SG와 EC2의 SG #
#2에서 본 SG 패턴:
ALB SG (sg-alb)
Inbound: 443 ← 0.0.0.0/0
Outbound: all
EC2 SG (sg-app)
Inbound: 8080 ← sg-alb ← ALB SG 자체
Outbound: allNLB는 다릅니다. NLB 자체는 SG가 없거나 (옛 NLB) 또는 한 SG만 (새 옵션). 그래서 EC2 SG가 NLB의 IP가 아닌 클라이언트 IP를 그대로 보게 됩니다. 운영에선 NLB 클라이언트 IP를 좁히기 위해 NACL이나 VPC 단 제어를 추가하기도 합니다.
자주 만나는 함정 #
1) ACM 인증서가 ISSUED 안 됨 #
원인 거의 99%:
- 검증 CNAME이 Route 53에 안 들어감
- CNAME이 잘못된 zone에 들어감 (
example.com이 아니라api.example.com의 zone) - TTL이 너무 길어서 전파 안 됨 → 5분 정도 기다림
2) CloudFront 인증서를 서울에서 발급 #
CloudFront는 us-east-1만. 콘솔에서 받을 때 우측 상단을 N. Virginia로.
3) HTTP 80 Listener 안 만들고 HTTPS만 #
사용자가 http://example.com으로 들어오면 timeout. 80 Listener의 redirect to HTTPS가 표준.
4) Health check path가 / #
/가 무거운 경우 (DB 호출 등)면 ALB health check가 DB를 30초마다 폭격합니다. /health 같은 가벼운 경로를 별도로 둡니다.
5) Target Group의 포트 vs Listener 포트 #
ALB Listener는 443인데 Target Group의 EC2는 8080일 수 있습니다. 두 포트는 별개입니다. Target Group 포트가 EC2가 listen 중인 포트입니다.
6) 502 Bad Gateway #
ALB → EC2 응답이 깨질 때. 흔한 원인:
- EC2의 keep-alive timeout < ALB idle timeout (60s 기본)
- EC2가 응답하기 전에 끊김
- EC2 SG가 ALB SG를 inbound에 안 받음
- EC2가 8080에서 listen 안 함
7) Sticky session으로 부하 쏠림 #
특정 인스턴스에만 요청이 쌓여 한 대만 CPU 100%가 됩니다. Stickiness를 끄고 stateless로 전환하는 것이 해결책입니다.
8) Cross-Zone 끔 #
옛 ALB의 cross-zone 옵션이 꺼져 있으면 AZ 단위 균등 분배. 한 AZ의 인스턴스가 적은데 트래픽은 같은 양 → 부하 쏠림. Cross-zone load balancing 켜기 (ALB는 기본 켜짐, NLB는 비용 때문에 기본 꺼짐).
9) Idle timeout의 함정 #
ALB의 idle timeout (기본 60s)이 백엔드 / 클라이언트의 keep-alive보다 짧으면 502. 보통 백엔드 keep-alive > ALB idle.
정리 #
이번 글에서 잡은 것:
- ELB = ALB / NLB / GWLB. 99% 일반 웹은 ALB
- ALB: L7, path / host / header 라우팅, WAF 연동, Lambda target, Cognito 인증
- NLB: L4, 고정 IP, 수백만 RPS, PrivateLink
- ALB 흐름 = Listener (받는 포트) → Listener Rule (path/host) → Target Group (보낼 대상) → Health Check
- Target Group의 target-type = instance / ip / lambda
- Listener Rule의 action = forward / redirect / fixed-response / authenticate
- HTTP → HTTPS redirect가 운영 표준 (80 Listener의 default action)
- Health check는 가벼운
/health, deep은 별도/health/deep - ACM = 무료 SSL 인증서 + 자동 갱신. DNS 검증 권장
- 리전 매핑. ALB는 같은 리전, CloudFront는 항상
us-east-1 - HTTPS 다는 6단계. 인증서 요청 → CNAME 검증 → ISSUED → Listener attach → 80 redirect → Route 53 Alias
- Security Policy로 허용 TLS 버전 제한
- 함정. 인증서 검증 실패, CloudFront 리전, 80 누락, health check 무거움, 포트 헷갈림, 502, sticky 쏠림, cross-zone, idle timeout
다음: CloudFront #
ALB까지는 잡았습니다. 마지막으로, 사용자에 가까이 캐시를 두는 단계, CloudFront입니다.
#7 CloudFront로 정적 사이트 배포에서는 Origin / Behavior / Cache Policy의 흐름, S3 + CloudFront 정적 호스팅 패턴, OAC로 S3를 안전하게 가리는 방법, 그리고 무효화 (invalidation) 까지 정리하겠습니다.