학습 목표

시나리오

HTTP로 응답하는 웹 애플리케이션을 배포합니다. 요구는 이렇습니다.

아키텍처 개요

구성 요소와 배치
구성 요소 배치 역할
Application Load Balancer 퍼블릭 서브넷(가용 영역 2곳) 인터넷 요청을 받아 대상 그룹으로 분배
대상 그룹 — 상태 확인으로 정상 인스턴스만 트래픽 대상에 유지
Auto Scaling 그룹 프라이빗 서브넷(가용 영역 2곳) 용량 유지와 확장·축소, 비정상 인스턴스 교체
시작 템플릿 — 인스턴스 사양·이미지·보안 그룹·사용자 데이터 정의
보안 그룹 2개 — 로드 밸런서용(인터넷 허용) · 인스턴스용(로드 밸런서 보안 그룹만 허용)

보안 그룹을 두 개로 나누고 인스턴스 쪽 소스를 로드 밸런서 보안 그룹 ID로 지정하는 것이 이 실습의 설계 요점입니다. 인스턴스가 늘거나 서브넷이 바뀌어도 규칙을 고칠 필요가 없습니다.

구성 단계

1 · 보안 그룹 두 개

create-security-groups.sh — 로드 밸런서용과 인스턴스용 (VPC ID는 실습 1의 출력값을 씁니다)
VPC_ID=vpc-0123456789abcdef0

ALB_SG=$(aws ec2 create-security-group \
  --group-name lab02-alb-sg \
  --description "ALB ingress from internet" \
  --vpc-id "$VPC_ID" \
  --query 'GroupId' --output text)

aws ec2 authorize-security-group-ingress \
  --group-id "$ALB_SG" \
  --protocol tcp --port 80 --cidr 0.0.0.0/0

APP_SG=$(aws ec2 create-security-group \
  --group-name lab02-app-sg \
  --description "App ingress from ALB only" \
  --vpc-id "$VPC_ID" \
  --query 'GroupId' --output text)

# 소스를 CIDR 이 아니라 보안 그룹으로 지정합니다.
aws ec2 authorize-security-group-ingress \
  --group-id "$APP_SG" \
  --ip-permissions "[{
      \"IpProtocol\": \"tcp\", \"FromPort\": 80, \"ToPort\": 80,
      \"UserIdGroupPairs\": [{\"GroupId\": \"$ALB_SG\"}]
    }]"

2 · 시작 템플릿

인스턴스에는 액세스 키를 심지 않습니다. 필요한 권한은 IAM 역할을 인스턴스 프로파일로 연결해 전달하고, 메타데이터는 IMDSv2를 요구하도록 설정합니다.

launch-template.json — 시작 템플릿 데이터 (AMI ID와 인스턴스 타입은 리전·용도에 맞춰 정합니다)
{
  "ImageId": "ami-0123456789abcdef0",
  "InstanceType": "t3.micro",
  "SecurityGroupIds": ["sg-0app000000000000"],
  "IamInstanceProfile": { "Name": "Lab02WebProfile" },
  "MetadataOptions": {
    "HttpTokens": "required",
    "HttpEndpoint": "enabled",
    "HttpPutResponseHopLimit": 1
  },
  "TagSpecifications": [
    {
      "ResourceType": "instance",
      "Tags": [{ "Key": "Name", "Value": "lab02-web" }]
    }
  ]
}
create-launch-template.sh — 시작 템플릿 생성
aws ec2 create-launch-template \
  --launch-template-name lab02-web-lt \
  --launch-template-data file://launch-template.json \
  --query 'LaunchTemplate.[LaunchTemplateId,LatestVersionNumber]' \
  --output table

3 · 로드 밸런서와 대상 그룹

create-alb.sh — 로드 밸런서 · 대상 그룹 · 리스너 (서브넷 ID는 실습 1의 출력값을 씁니다)
ALB_ARN=$(aws elbv2 create-load-balancer \
  --name lab02-alb \
  --type application \
  --scheme internet-facing \
  --subnets subnet-0pub00000000000a subnet-0pub00000000000b \
  --security-groups "$ALB_SG" \
  --query 'LoadBalancers[0].LoadBalancerArn' --output text)

TG_ARN=$(aws elbv2 create-target-group \
  --name lab02-tg \
  --protocol HTTP --port 80 \
  --vpc-id "$VPC_ID" \
  --target-type instance \
  --health-check-protocol HTTP \
  --health-check-path /health \
  --health-check-interval-seconds 15 \
  --health-check-timeout-seconds 5 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 2 \
  --query 'TargetGroups[0].TargetGroupArn' --output text)

aws elbv2 create-listener \
  --load-balancer-arn "$ALB_ARN" \
  --protocol HTTP --port 80 \
  --default-actions "Type=forward,TargetGroupArn=$TG_ARN"

4 · Auto Scaling 그룹

create-asg.sh — 다중 가용 영역 Auto Scaling 그룹과 ELB 상태 확인
aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name lab02-asg \
  --launch-template "LaunchTemplateName=lab02-web-lt,Version=\$Latest" \
  --min-size 2 --max-size 6 --desired-capacity 2 \
  --vpc-zone-identifier "subnet-0prv00000000000a,subnet-0prv00000000000b" \
  --target-group-arns "$TG_ARN" \
  --health-check-type ELB \
  --health-check-grace-period 300 \
  --default-instance-warmup 180

상태 확인 유형에는 EC2, EBS, ELB, VPC_LATTICE가 있고 EC2는 기본이며 끌 수 없습니다. ELB를 추가해야 인스턴스는 살아 있는데 애플리케이션이 응답하지 못하는 상태가 교체 대상이 됩니다.

5 · 스케일링 정책

scaling-policy.sh — 대상당 요청 수를 목표로 하는 대상 추적 정책 (목표값은 애플리케이션 특성에 맞춰 정합니다)
ALB_LABEL=$(aws elbv2 describe-load-balancers --load-balancer-arns "$ALB_ARN" \
  --query 'LoadBalancers[0].LoadBalancerArn' --output text | cut -d: -f6)
TG_LABEL=$(echo "$TG_ARN" | cut -d: -f6)

aws autoscaling put-scaling-policy \
  --auto-scaling-group-name lab02-asg \
  --policy-name lab02-track-requests \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration "{
      \"TargetValue\": 500.0,
      \"PredefinedMetricSpecification\": {
        \"PredefinedMetricType\": \"ALBRequestCountPerTarget\",
        \"ResourceLabel\": \"$ALB_LABEL/$TG_LABEL\"
      }
    }"

검증 방법

verify.sh — 분산 상태와 대상 건강 상태 확인
# 1) 인스턴스가 두 가용 영역에 나뉘어 있는지
aws autoscaling describe-auto-scaling-groups \
  --auto-scaling-group-names lab02-asg \
  --query 'AutoScalingGroups[0].Instances[].[InstanceId,AvailabilityZone,LifecycleState,HealthStatus]' \
  --output table

# 2) 대상 그룹의 상태 확인 결과
aws elbv2 describe-target-health \
  --target-group-arn "$TG_ARN" \
  --query 'TargetHealthDescriptions[].[Target.Id,TargetHealth.State,TargetHealth.Reason]' \
  --output table

# 3) 로드 밸런서의 DNS 이름으로 실제 응답 확인
aws elbv2 describe-load-balancers \
  --load-balancer-arns "$ALB_ARN" \
  --query 'LoadBalancers[0].[DNSName,State.Code,AvailabilityZones[].ZoneName]' \
  --output json

장애를 일부러 만들어 본다

구성이 실제로 동작하는지는 실패를 만들어 봐야 압니다.

  1. 인스턴스 하나에서 애플리케이션 프로세스를 중지시킵니다.
  2. 대상 그룹의 상태가 unhealthy로 바뀌는지 봅니다.
  3. Auto Scaling이 그 인스턴스를 종료하고 새로 시작하는지 봅니다.
  4. 그동안 로드 밸런서가 남은 인스턴스로만 트래픽을 보내는지 확인합니다.
force-replace.sh — 인스턴스를 비정상으로 표시해 교체를 관찰 (인스턴스 ID는 예시입니다)
aws autoscaling set-instance-health \
  --instance-id i-0123456789abcdef0 \
  --health-status Unhealthy \
  --no-should-respect-grace-period

aws autoscaling describe-scaling-activities \
  --auto-scaling-group-name lab02-asg \
  --max-items 5 \
  --query 'Activities[].[StartTime,StatusCode,Description]' \
  --output table

비용 고려

요금이 발생하는 구성 요소
구성 요소 요금 항목의 성격
Application Load Balancer 가동 시간과 처리한 용량 단위
EC2 인스턴스 실행 시간. 최소 용량이 곧 하한 비용이다
EBS 볼륨 인스턴스를 중지해도 계속 발생한다
가용 영역 간 전송 교차 영역 분배로 경계를 넘는 트래픽에 붙는다
Auto Scaling 서비스 자체로는 항목이 늘지 않는다. 늘어난 인스턴스가 항목이다

비용 관점에서 중요한 설정은 최소 용량입니다. 가용성을 위해 최소 2대를 유지한다면 그것이 상시 비용의 바닥이 됩니다. 예측 가능한 한산한 시간대가 있다면 예약 스케일링으로 최소 용량을 낮출 수 있습니다 (케이스 7).

프로덕션 고려사항

정리 — 리소스 삭제

cleanup.sh — 삭제 순서
aws autoscaling delete-auto-scaling-group \
  --auto-scaling-group-name lab02-asg \
  --force-delete

aws elbv2 delete-listener --listener-arn "$LISTENER_ARN"
aws elbv2 delete-load-balancer --load-balancer-arn "$ALB_ARN"
aws elbv2 delete-target-group --target-group-arn "$TG_ARN"

aws ec2 delete-launch-template --launch-template-name lab02-web-lt

# 보안 그룹은 참조가 모두 사라진 뒤에야 삭제됩니다.
aws ec2 delete-security-group --group-id "$APP_SG"
aws ec2 delete-security-group --group-id "$ALB_SG"

자주 하는 실수

자주 나오는 실수와 증상
실수 증상 고치는 법
로드 밸런서를 서브넷 하나에만 등록 가용 영역 장애 시 전면 중단 둘 이상의 가용 영역 서브넷을 지정한다
Auto Scaling 그룹의 서브넷이 한 가용 영역에만 인스턴스가 전부 같은 곳에 몰린다 --vpc-zone-identifier에 여러 가용 영역 서브넷을 넣는다
상태 확인 유형을 EC2만 사용 애플리케이션이 죽어도 교체되지 않는다 --health-check-type ELB
유예 기간이 기동 시간보다 짧음 새 인스턴스가 준비 전에 비정상으로 교체되어 반복된다 기동 시간을 재고 유예 기간을 늘린다
인스턴스 보안 그룹이 0.0.0.0/0 허용 로드 밸런서를 우회한 직접 접근이 가능해진다 소스를 로드 밸런서 보안 그룹 ID로 지정한다
상태 확인 경로가 정적 파일 의존 서비스가 끊긴 인스턴스가 계속 정상으로 남는다 애플리케이션이 실제로 일할 수 있는지 반환하는 경로로 바꾼다
단순 조정 정책으로 1대씩 확장 급증을 따라가지 못한다 대상 추적 정책으로 바꾼다(케이스 7)

이어서 볼 곳

공식 문서 출처