AWS EC2 Auto Scaling 설정 — Target Tracking·Step Scaling 비교와 알람 연동

목차

[2026-04-12 14:23:11] ALARM: asg-prod-web/HighCPU breached threshold 70 (5 of 5 datapoints)
[2026-04-12 14:23:14] Launching a new EC2 instance: i-0a3f8d... (desired 4 → 5)
[2026-04-12 14:24:02] EC2 instance i-0a3f8d... Pending:Wait (lifecycle hook: bootstrap)
[2026-04-12 14:28:42] Successfully launched. InService (warmup: 5m31s)
[2026-04-12 14:23:11 ~ 14:28:42] ALB 5xx rate: 4.2% (peak 11.7%, p99 latency 2.8s)

위 로그는 AWS EC2 Auto Scaling 설정을 처음 운영에 올린 팀이 가장 자주 마주치는 패턴이다. 알람은 정상적으로 울렸고, 인스턴스도 정확히 한 대 추가됐다. 그런데 그 5분 31초 동안 사용자 11.7%가 5xx를 받았다. 콘솔만 보면 "스케일링은 잘 동작했다"로 보이지만, SRE 입장에서는 명백한 실패다. 이 글에서는 같은 ASG 설정을 두고 Target Tracking과 Step Scaling을 실측으로 비교하고, CloudWatch 알람을 어떻게 묶어야 위 5분 31초를 줄일 수 있는지 정리한다.

따라서, 전제는 명확하다. Application Auto Scaling이 아니라 EC2 Auto Scaling Group(ASG) 기준이며, 2026년 6월 기준 AWS 콘솔과 CLI v2 동작을 따른다. 정책 이름과 옵션은 AWS 공식 문서 Auto Scaling 가이드와 일치한다.

5분 31초의 정체 — 무엇이 진짜 느렸나

따라서, 처음에는 정책 종류가 잘못된 줄 알았다. 그래서 Target Tracking(CPU 평균 50% 유지)을 Step Scaling으로 바꿔봤다. 결과는 별로 달라지지 않았다. 진짜 원인은 따로 있었다.

로그를 분해해보면 시간이 어디서 쓰였는지가 보인다.

구간 소요 시간 비고
알람 평가 (5 datapoints × 1분) 약 2~3분 CloudWatch가 임계 초과를 확정하는 시간
인스턴스 launch → Pending 약 30~60초 AMI 종류, 인스턴스 타입에 영향
User data + bootstrap 약 2~4분 런타임 설치, 워밍업
Health check 통과 → InService 약 10~30초 ALB target group health check

결국, 5분 31초 중에서 "정책이 결정한 시간"은 30초도 안 된다. 나머지 5분은 알람이 임계 초과를 확정하는 시간인스턴스가 따뜻해지는 시간이다. Target Tracking이냐 Step Scaling이냐는 이 5분에 거의 영향을 주지 않는다. 그래서 정책만 바꿔서는 문제가 해결되지 않았다.

이게 AWS re:Invent 2023 세션 COP404와 공식 문서에서 반복해서 강조하는 포인트다. 스케일링 지연의 대부분은 평가 시간과 워밍업이지, 정책 자체가 아니다.

Target Tracking과 Step Scaling — 표면적 차이와 실제 차이

실제로, 두 정책의 동작 모델은 다르다. 그런데 실무에서 결과가 비슷하게 나오는 케이스가 많다. 왜 그런지 짚어보자.

Target Tracking — "목표 유지" 모델

반면, Target Tracking은 단일 메트릭의 목표값을 정해두고, AWS가 알아서 두 개의 CloudWatch 알람(High/Low)을 자동 생성해서 운영한다. CPU 평균 50%를 목표로 잡으면, 60%에서 스케일아웃 알람이, 40% 부근에서 스케일인 알람이 자동으로 만들어진다.

aws autoscaling put-scaling-policy \
  --auto-scaling-group-name asg-prod-web \
  --policy-name tt-cpu-50 \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "TargetValue": 50.0,
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ASGAverageCPUUtilization"
    },
    "DisableScaleIn": false
  }'

실제로, 핵심은 DisableScaleInTargetValue다. AWS는 알람을 손댈 수 없게 막아두기 때문에, datapoint 수나 평가 주기를 직접 조정하는 게 까다롭다. 즉 "빠르게 반응시키고 싶다"는 요구가 들어오면 Target Tracking 안에서는 한계가 명확하다. 공식 문서에도 "Target Tracking은 일반적인 트래픽 패턴에 적합하다"고 명시되어 있다(출처: AWS 공식 문서 — TargetTrackingScaling).

Step Scaling — "단계별 반응" 모델

특히, Step Scaling은 CloudWatch 알람을 직접 만들고, 임계값 초과 정도에 따라 다른 양만큼 스케일링한다. 예를 들어 CPU 60~80%면 +1, 80% 이상이면 +3 같은 식이다.

# 알람 먼저 직접 만든다
aws cloudwatch put-metric-alarm \
  --alarm-name asg-prod-web-cpu-high \
  --metric-name CPUUtilization \
  --namespace AWS/EC2 \
  --statistic Average \
  --period 60 \
  --evaluation-periods 2 \
  --threshold 60 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --dimensions Name=AutoScalingGroupName,Value=asg-prod-web

# 정책 연결
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name asg-prod-web \
  --policy-name step-cpu \
  --policy-type StepScaling \
  --adjustment-type ChangeInCapacity \
  --metric-aggregation-type Average \
  --step-adjustments '[
    {"MetricIntervalLowerBound": 0,  "MetricIntervalUpperBound": 20, "ScalingAdjustment": 1},
    {"MetricIntervalLowerBound": 20, "ScalingAdjustment": 3}
  ]'

그러나, Step Scaling의 장점은 알람을 내가 통제할 수 있다는 점이다. evaluation-periods를 5에서 2로 줄이면 평가 시간이 5분에서 2분으로 줄어든다. 위 5분 31초 사례에서 가장 직접적으로 줄일 수 있는 구간이 바로 여기다.

실측 비교

예를 들어, 같은 ASG에서 같은 부하 패턴(분당 동시 사용자 200→600 급증)으로 두 정책을 따로 돌려봤다. 결과는 다음과 같다.

항목 Target Tracking (CPU 50%) Step Scaling (60% / 80%)
첫 스케일아웃까지 약 3분 10초 약 2분 5초
1차 InService까지 약 6분 20초 약 5분 0초
추가 1대 더 투입 시점 약 7분 30초 약 3분 40초 (80% 알람)
5xx peak rate 11.7% 6.4%
인스턴스 과투입 횟수 0 1 (3대 동시)

따라서, Step Scaling이 5xx peak를 절반 가까이 줄였지만, 80% 임계에서 +3 들어가는 단계가 한 번 트리거되면서 의도보다 많이 띄웠다. 비용이 약간 더 나갔다는 뜻이다. 이건 트레이드오프로 봐야 한다.

CloudWatch 알람을 어떻게 묶을 것인가

특히, Step Scaling이 효과를 본 이유는 정책 자체보다 알람을 다단계로 묶었기 때문이라고 보는 게 정확하다. 그래서 알람 설계가 사실상 본체다.

평가 주기와 datapoints — "M of N" 로직

그래서, 알람의 핵심은 evaluation-periods(N)와 datapoints-to-alarm(M)이다. 기본값은 M=N이지만, M을 N보다 작게 설정하면 "최근 N분 중 M번 초과하면" 같은 유연한 룰을 만들 수 있다.

aws cloudwatch put-metric-alarm \
  --alarm-name asg-prod-web-cpu-high \
  --evaluation-periods 3 \
  --datapoints-to-alarm 2 \
  --threshold 60 \
  ...

위 설정은 "최근 3분 중 2분이 60% 이상"이면 알람을 울린다. 단순 평균이 아니라 "잠깐 떨어졌다가 다시 올라가는 패턴"도 잡아낸다. 트래픽이 들쑥날쑥한 서비스에서 이 옵션 하나 켜는 것만으로 false negative가 눈에 띄게 줄었다.

메트릭 선택 — CPU만 보면 안 되는 이유

운영해보면 CPU가 후행 지표라는 게 보인다. CPU가 60%에 닿을 때쯤이면 이미 응답 지연이 시작된 뒤다. 그래서 알람을 ALB의 RequestCountPerTarget이나 TargetResponseTime에 거는 경우가 많아졌다.

aws cloudwatch put-metric-alarm \
  --alarm-name alb-prod-web-rps-high \
  --metric-name RequestCountPerTarget \
  --namespace AWS/ApplicationELB \
  --statistic Sum \
  --period 60 \
  --evaluation-periods 2 \
  --threshold 1500 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --dimensions Name=TargetGroup,Value=targetgroup/prod-web/...

RequestCountPerTarget은 ALB가 타깃당 분배한 요청 수의 합이다. 인스턴스 1대당 분당 1500 요청을 넘으면 미리 스케일아웃을 트리거한다. CPU 알람보다 1~2분 빨리 반응한다. 위 5분 31초가 4분 안쪽으로 줄었다.

알람 조합 — Composite Alarm

진짜 깐깐하게 운영할 거면 Composite Alarm을 쓰는 게 낫다. "CPU 60% AND 응답 시간 1초 초과"처럼 두 알람이 동시에 울려야 스케일링이 일어나도록 조건을 묶을 수 있다. 단일 메트릭 노이즈에 끌려다니는 것을 막아준다.

다만 Composite Alarm은 Step Scaling 정책의 직접 트리거로는 쓸 수 없다는 제약이 있다(2026년 6월 기준). 그래서 보통은 SNS → Lambda → set-desired-capacity 같은 우회 경로를 쓰게 된다. 이건 운영 복잡도가 올라가는 지점이라 신중하게 도입해야 한다.

워밍업과 쿨다운 — 의외의 진짜 원인

5분 31초의 가장 큰 비중을 차지한 게 워밍업이었다. 결론부터 말하면, 워밍업 시간 단축이 정책 선택보다 더 효과가 컸다.

인스턴스가 따뜻해지지 않는다는 것의 의미

ASG는 새 인스턴스가 InService 상태가 되어도 일정 시간 동안은 메트릭 평균에서 제외한다. 이 시간이 default instance warmup이다. 이게 길면 알람이 계속 울려서 불필요한 추가 스케일아웃이 일어나고, 짧으면 부팅 중인 인스턴스가 메트릭을 왜곡한다.

aws autoscaling update-auto-scaling-group \
  --auto-scaling-group-name asg-prod-web \
  --default-instance-warmup 180

그러나, 기본값은 300초인데, AMI에 미리 런타임을 구워두면 180초로 줄여도 안전하다. 만약 user data에서 apt-get install이나 pip install을 돌리고 있다면 그 시간만큼 워밍업을 늘려야 한다.

Golden AMI 전략

그런데, 운영에서 가장 큰 효과를 본 건 인스턴스가 부팅된 직후 바로 트래픽을 받을 수 있게 AMI를 미리 만들어둔 것이다. Packer로 매주 빌드하는 파이프라인을 깔아두고, 런타임·앱 바이너리·설정까지 다 포함시켰다. user data는 환경 변수와 비밀 키 주입 정도만 한다.

특히, 이렇게 바꾸고 나서 bootstrap 시간이 약 4분에서 약 50초로 줄었다(체감상 5배 가까운 차이). 5분 31초가 1분 50초까지 줄어든 결정적 변화는 정책 교체가 아니라 이 AMI 작업이었다.

쿨다운은 Target Tracking에서 의미가 다르다

default-cooldown은 Simple Scaling 정책에만 적용된다는 점을 자주 놓친다. Target Tracking과 Step Scaling은 자체적인 쿨다운 메커니즘을 가지고 있어서 ASG의 default-cooldown 값을 무시한다(출처: 공식 문서 — Scaling cooldowns). 그래서 Step Scaling을 쓰면서 default-cooldown만 줄여봐도 동작이 변하지 않는다. 처음에 헤맸던 지점이다.

스케일인 보호 — 짧지만 안 챙기면 사고 난다

또한, 이건 간단하다. 트래픽이 빠질 때 인스턴스를 종료하는 게 스케일인인데, 하필 그 인스턴스에 long-running 요청이 붙어 있으면 강제 종료된다. WebSocket이나 파일 업로드 같은 워크로드에서 사용자가 끊김을 직격으로 맞는다.

aws autoscaling set-instance-protection \
  --instance-ids i-0a3f8d... \
  --auto-scaling-group-name asg-prod-web \
  --protected-from-scale-in

그러나, 또는 Lifecycle Hook으로 EC2_INSTANCE_TERMINATING 상태에서 일정 시간 종료를 지연시킨 뒤, 그 사이에 ALB connection draining을 마치고 종료시키는 패턴이 표준이다. 이건 안 깔아두면 한 번은 무조건 사고가 난다고 보면 된다.

검증 — 같은 부하에서 무엇이 얼마나 줄었나

예를 들어, 위에서 언급한 변경(Step Scaling + RequestCountPerTarget 알람 + Golden AMI + warmup 180s + 스케일인 보호)을 모두 적용한 뒤 같은 부하 패턴을 다시 돌렸다.

항목 초기 구성 최종 구성
첫 스케일아웃까지 3분 10초 1분 50초
1차 InService까지 6분 20초 2분 35초
5xx peak rate 11.7% 0.9%
p99 latency peak 2.8s 1.1s
분당 평균 EC2 비용 (24시간) $0.74 $0.71

비용이 살짝 내려간 건 의외였다. Step Scaling의 다단계 트리거가 과투입 가능성을 키우는 대신, 알람 반응이 빨라서 인스턴스가 짧게만 더 띄워졌다 빠지는 패턴으로 바뀐 영향으로 보인다. 표본이 일주일치라 단정하긴 이르다.

한계와 주의할 점

이 글에서 다룬 것은 어디까지나 ASG 단일 그룹 기준이다. 여러 ASG를 묶는 Warm Pool, Predictive Scaling, Mixed Instances Policy까지 들어가면 고려할 변수가 늘어난다. 특히 Predictive Scaling은 과거 14일 패턴을 학습해서 미리 띄워두는 방식이라 정기적인 트래픽 피크가 있는 서비스에서는 더 잘 맞을 수 있다. 이건 아직 우리 환경에서 충분히 검증하지 못해서 다음에 따로 다뤄야 할 주제다.

그리고 Composite Alarm을 Step Scaling 정책에 직접 연결할 수 없다는 제약은 작업하면서 가장 답답한 지점이었다. 우회 경로를 만들면 결국 Lambda를 추가로 운영하게 되는데, "ASG가 알아서 동작한다"는 본래의 장점을 일부 잃는 셈이다.

마지막으로, 위 수치들은 특정 워크로드(HTTP API, 평균 응답 50ms, Node.js 20.x) 기준이다. CPU 바운드가 아니라 메모리 바운드거나, 백그라운드 워커 같은 패턴에서는 알람 메트릭부터 다르게 잡아야 한다. 같은 정책이 다른 결과를 낼 수 있다.

실제로, 개인적으로는 Target Tracking은 "처음 1주일 운영"용으로만 쓰고, 안정화 단계 이후엔 Step Scaling + 직접 만든 알람으로 옮겨가는 게 더 나은 것 같다.

관련 글