🔥 Auto Scaling Group: 수요에 따라 늘리고 줄이는 법
강의 목차

콘솔에서 "Auto Scaling Group 만들기"를 누른 첫 화면에는 Launch Template을 고르라는 입력란만 떠 있었다. Launch Configuration을 만들어 두면 자동으로 잡아주는 줄 알았는데, "이 화면에서 새 Launch Configuration은 더 이상 만들지 못합니다"라는 회색 안내가 함께 떠 있었다. AWS가 2024년 10월 1일부터 새 계정에서는 Launch Configuration 생성을 막았기 때문이다(What's New 2023-12, Compute Blog). 기존 계정에 살아 있는 LC는 당분간 더 쓸 수 있지만, 새로 시작하는 사람이 마주하는 첫 번째 사실은 ASG는 Launch Template 위에서만 자란다다.
이 한 줄에서 출발해 ASG가 어떤 다섯 부품으로 이루어져 있는지, 늘어나고 줄어드는 신호를 어떻게 받아들이는지, 그리고 어디서 "관리형이 무료라는 말"이 청구서로 새는지를 다음 다섯 단원에서 따라간다. 코드는 안 쓴다. ASG는 손가락으로 쓰는 게 아니라 인스턴스 타입: CPU·메모리·네트워크의 조합과 AMI: 머신 이미지의 라이프사이클, EBS vs Instance Store: 디스크는 어디 붙어 있는가, Spot, On-Demand, Reserved: 세 가지 가격 모델이 합쳐지는 곳이고, 이 합쳐짐을 보고 나면 콘솔의 입력란이 무엇을 뜻하는지가 한 번에 닿는다.
ASG는 다섯 부품의 묶음이다

ASG를 한 줄로 정의하면 "EC2 인스턴스를 몇 대 유지할지 자동으로 관리하는 그룹"이다. 그런데 이 한 줄은 너무 압축돼 있어서, 실제로 ASG를 만드는 콘솔 단계에서 마주치는 다섯 입력란이 어떻게 한 줄 안에 다 들어가는지 잘 보이지 않는다. 다섯 부품을 따로 풀어 봐야 한다.
첫째는 Launch Template이다. 인스턴스를 어떻게 띄울지 (AMI, 인스턴스 타입, EBS 볼륨, IAM 역할, 보안 그룹, 사용자 데이터를) 한 번 적어 두면 ASG가 이걸 그대로 복사해서 새 인스턴스를 만든다. Launch Configuration이라는 옛 형태가 있었지만 새로운 EC2 기능을 따라가지 못해서 AWS가 2024년 10월 1일부로 새 계정에서는 만들지 못하게 막았다. 왜 이렇게까지 막았냐면, Launch Template이 한 ASG에서 여러 인스턴스 타입을 섞을 수 있게 해 주는 데 비해 Launch Configuration은 한 가지 타입만 받기 때문이다. 그래서 새로 시작하는 모든 ASG는 Launch Template만 본다고 생각해도 된다.
둘째는 용량 손잡이 세 개, 즉 Min·Max·Desired다. 최소 몇 대까지 줄어들 수 있는지(MinSize), 최대 몇 대까지 늘어날 수 있는지(MaxSize), 지금 이 순간 ASG가 목표로 하는 대수(DesiredCapacity) 세 숫자다. 왜 셋이 다 따로 있냐면, Min/Max는 경계고 Desired는 현재 상태를 가리키기 때문이다. 예를 들어 Min=2, Max=10, Desired=4로 두면 평소엔 4대를 굴리다가, 스케일링 정책이 트리거되면 Desired가 5·6·7…로 올라가고, 트래픽이 빠지면 다시 4·3·2까지 내려간다. 그런데 2 밑으로는 절대 안 내려간다. 새벽 3시 트래픽이 0이어도 2대는 살아 있어 청구서에 한 시간씩 올라간다. 이게 "Min을 너무 높게 잡았다"가 한 달 청구서에서 가장 자주 새는 대목이다.
셋째는 분포 대상으로, 어느 VPC의 어느 Subnet들에 인스턴스를 흩뿌릴지다. ASG는 입력으로 받은 Subnet 목록을 가용영역 단위로 묶어서 인스턴스를 균등 분포한다. Subnet을 세 가용영역(ap-northeast-2a/b/c)에 걸쳐 세 개 넣고 Desired=6으로 두면 가용영역마다 2대씩 들어간다. 한 가용영역이 통째로 내려가면 ASG가 자동으로 살아 있는 두 영역에서 부족한 만큼 다시 띄운다. 단일 가용영역에만 Subnet을 넣어 두면 이 회복이 안 일어나니, ASG의 가장 기본적인 가치인 가용영역 장애 흡수가 통째로 사라진 셈이다.
넷째는 Health Check, 다섯째는 스케일링 정책이다. 두 부품은 다음 단원에서 따로 본다. 이 다섯이 합쳐져서 비로소 한 ASG가 작동한다.
여기서 잠깐 짚어 두고 싶은 사실 한 가지가 있다. ASG 자체는 무료다. AWS가 ASG라는 그룹을 굴려 주는 데에는 따로 시간 요금을 받지 않는다. 청구서에 올라오는 건 ASG가 띄운 EC2 인스턴스, 그 인스턴스가 매단 EBS 볼륨, 그리고 ASG가 신호로 쓰는 Metric: 숫자 하나가 찍히는 흐름과 Alarm: 임계값을 걸고 알림을 받는 법이다. "관리형 자동 스케일링"이라는 이름이 무료라는 인상을 주지만, 그 이름이 가리는 건 그 안에서 도는 자원들의 청구서다.
늘어나는 흐름: 한 신호가 새 인스턴스가 되는 길

ASG가 한 대를 더 띄우는 순간을 한 장면으로 풀어 보자. 평일 오후 두 시 반에 갑자기 트래픽이 두 배로 들어오고, ALB 뒤의 인스턴스 4대가 평균 CPU 75%까지 올라간다. 60초 뒤, CloudWatch가 그 평균을 한 점으로 찍는다. CloudWatch Alarm은 "5분 연속 70% 초과면 알람을 켠다"는 규칙을 들고 그 점들을 보고 있다가, 다섯 점 중 다섯 점이 모두 70%를 넘은 시각에 알람을 ALARM 상태로 바꾼다.
알람이 켜지면, 그 알람에 연결돼 있는 스케일링 정책이 작동한다. 스케일링 정책은 "Desired를 5로 올려라" 같은 명령을 ASG에게 보낸다. ASG는 Desired가 4에서 5로 바뀐 걸 보고, Launch Template을 꺼내 새 인스턴스를 RunInstances API로 띄운다. 이 인스턴스가 부팅을 시작하고, 사용자 데이터 스크립트가 돌고, 애플리케이션이 일어나는 데 보통 90초에서 3분이 든다.
여기서 가장 자주 헷갈리는 게 부팅 중인 인스턴스를 ASG가 어떻게 다루느냐다. 새로 띄운 인스턴스는 곧장 InService 상태가 되지 않는다. 그 사이 단계가 두 개 있다. 첫 번째는 Pending, 즉 EC2 인스턴스가 아직 running으로 진입하지 않은 상태. 두 번째는 Pending:Wait, 라이프사이클 훅(lifecycle hook)이 걸려 있을 때 사용자가 마무리 작업을 할 시간을 주는 상태. ASG는 이 단계에 있는 인스턴스를 ALB 타깃 그룹에는 등록하되, 자체 상태가 InService로 넘어가기 전까지는 헬스 체크가 통과하지 않은 것으로 본다. ALB는 자기 health check가 통과해야 그 인스턴스에 트래픽을 보내기 시작한다.
내가 처음 이 흐름을 설계할 때 한 번 부딪힌 함정이 있다. 우리 앱은 부팅에 약 2분이 들었는데, 콘솔에서 ASG를 만들 때 Health Check Grace Period를 따로 손보지 않았다. 그러면 콘솔 기본값 300초(5분)가 들어간다(공식 문서). 부팅이 2분이고 grace period가 5분이니 충분해 보이지만, CLI나 SDK로 만들면 기본값이 0초다. CloudFormation/Terraform으로 만든 ASG에서 grace period를 명시 안 해 두면, 인스턴스가 일어나기도 전에 ELB health check가 실패하고 ASG가 그 인스턴스를 unhealthy로 마킹해 30초 만에 강제 종료한다. 그러고는 다시 새 인스턴스를 띄우고, 또 죽이고 무한 교체 루프에 머문다. 인스턴스 청구서만 두 시간 만에 평소 하루치를 찍었다. 콘솔과 코드의 기본값이 다른 게 콘솔 사용자에게는 잘 보이지 않는 흙구덩이다.
여기서 이어서 본 건 Cooldown이다. 단순 스케일링(Simple Scaling) 정책은 한 번 발동하고 나면 기본 300초 동안 새 스케일링을 안 받는다. 왜 300초 동안 멈추냐면, 부팅에 시간이 드는 인스턴스가 아직 부하를 못 받고 있는데 알람이 또 울려서 두 번째 인스턴스를 띄우면 과조절(over-scaling)이 일어나기 때문이다. 평균 CPU 70%는 60초만에 50%로 내려가지 않으니, 단순 스케일링은 그 사이 또 한 번 발동한다. Cooldown이 그 두 번째 트리거를 흡수해 준다. Target Tracking과 Step Scaling은 같은 문제를 Warmup이라는 다른 이름으로 푼다. Cooldown이 ASG 전체를 멈추는 데 비해, Warmup은 새로 일어나는 인스턴스의 메트릭을 평균 계산에서 제외한다. 같은 문제, 다른 길이다.
Health Check 두 갈래: 누가 죽었는지 누구에게 묻나
Health Check는 ASG 안에서 살아 있는 인스턴스만 InService로 두는 메커니즘이다. 죽은 인스턴스는 Unhealthy 마킹 → Terminating → 새 인스턴스로 교체 흐름을 탄다. 이 흐름의 입력 신호는 EC2와 ELB 두 갈래가 핵심이고, 그 외에도 VPC Lattice, EBS 볼륨 상태, 사용자 정의 health check까지 받을 수 있다. 보통 운영에서 부딪히는 두 갈래부터 짚는다.
첫 번째는 EC2 Status Check다. EC2 자체가 매 분 인스턴스 두 가지를 점검한다. System Status Check(호스트 머신·네트워크 문제)와 Instance Status Check(게스트 OS 문제). 둘 중 하나가 impaired 상태로 잡히면 ASG가 몇 분 기다린 다음 그 인스턴스를 unhealthy로 마킹한다. 이건 항상 켜져 있고, 끌 수 없다.
두 번째는 ELB Health Check다. ALB나 NLB 같은 로드밸런서가 자기 타깃 그룹 안 인스턴스에 주기적으로 HTTP/HTTPS/TCP 프로브를 보낸다. ALB는 "성공으로 볼 HTTP 코드"를 따로 설정할 수 있고 기본값이 200이다. NLB는 TCP·HTTP·HTTPS 셋 중 어느 것이든 쓸 수 있다. 임계값을 넘게 실패하면 unhealthy로 마킹한다. 이게 기본적으로는 ASG가 무시한다는 사실이 두 번째 함정이다. 콘솔에서 "Health check type: ELB"를 명시적으로 켜야 비로소 ASG가 ELB의 unhealthy 신호에 반응한다. 안 켜 두면 어떤 일이 일어나냐면, 앱이 데드락에 걸려 HTTP 200을 못 보내는데 EC2 status check는 통과하니 ASG는 그 인스턴스를 멀쩡하다고 본다. ALB는 트래픽을 보내지 않고, ASG는 새 인스턴스를 띄우지 않고, 사용자만 Latency 그래프가 솟구치는 걸 본다.
내가 한 번 이 함정에 빠진 일을 차근차근 풀어 보자. Java 애플리케이션이 GC 락에 걸려 30초 동안 응답을 못 했다. ALB의 health check 임계값은 "연속 2회 실패 = unhealthy"이고 30초 간격이라, 1분 만에 ALB가 그 인스턴스를 빼냈다. 그런데 ASG는 ELB health check를 안 켜 둬서 그 인스턴스를 살아 있다고 봤다. 결과적으로 ALB target group에 6대 중 5대만 남아 트래픽을 받고, ASG의 InService 카운트는 그대로 6이라 새 인스턴스를 안 띄웠다. 1분이 5분이 되고, 사용자 절반이 5xx를 받았다. 콘솔에서 Health check type을 ELB로 바꾸고 grace period를 인스턴스 부팅 시간(우리 앱은 90초)에 맞춰 120초로 잡고 나서야 ASG가 자기 일을 시작했다.
여기서 두 갈래가 합쳐지는 한 줄 결론을 적어 둔다. EC2 status check만 보면 OS 죽음만 잡고, ELB health check까지 켜야 앱 죽음을 잡는다. 둘 다 켜야 양쪽을 모두 검출한다.
여기서 또 한 가지 짚을 게 있다. Health check가 unhealthy를 잡으면 ASG가 "그 인스턴스 죽이고 다시 띄우기"를 한다. 죽이는 건 30초 만이지만 새로 띄우는 건 또 90초~3분. 그 사이 InService 인스턴스 수가 Desired보다 잠깐 적다. 사용자 트래픽이 그대로 들어오면 남은 인스턴스에 부하가 더 몰리고, CPU가 또 임계값 위로 올라가 스케일 아웃 알람이 트리거되고, 새 인스턴스 두 대가 동시에 일어난다. 그래서 Health check 기반 교체와 부하 기반 스케일 아웃은 서로 영향을 준다. ASG의 두 메커니즘은 따로 도는 게 아니라 같은 인스턴스 풀을 공유한다.
스케일링 정책 네 가지: 입력 신호의 형태에 따라

ASG가 Desired를 늘리고 줄이는 결정 규칙이 스케일링 정책이다. 네 갈래가 있고, 입력 신호의 형태가 달라서 쓰는 곳이 다르다. 네 갈래를 한 표로 묶기 전에, 각각이 무엇을 보고 어떻게 반응하는지부터 짚는다.
첫 번째는 Simple Scaling, 가장 오래된, 가장 단순한 길이다. CloudWatch Alarm 한 개에 "Desired를 +1 / +2 / -1" 같은 한 가지 동작을 묶어 둔다. 알람이 켜지면 그 동작 한 번만 한다. 다음 번 트리거는 cooldown(기본 300초)이 지나야 받는다. 단순한데 부하의 크기를 못 본다는 한계가 있다. CPU가 75%여도 95%여도 똑같이 +1대만 추가한다. AWS 공식 문서가 "이 정책은 Step Scaling/Target Tracking이 더 낫다"고 명시할 만큼 권장도가 낮지만, 한 가지 동작만 필요한 단순한 상황에서는 여전히 가장 짧은 설정이다.
두 번째는 Step Scaling, 부하의 크기를 보고 단계별로 다르게 반응하는 길이다. 한 알람에 단계(step)를 여러 개 매단다. 예를 들어 "70~80%면 +1대, 80~90%면 +2대, 90% 이상이면 +4대"처럼. 알람이 어느 구간에서 발동했는지에 따라 추가량이 다르다. Cooldown 대신 각 인스턴스의 Warmup 시간 동안 그 인스턴스의 메트릭을 평균에서 제외한다. 부팅 중인 인스턴스가 평균을 낮추지 못하니, 또 다른 알람이 잘못 트리거되는 걸 막는다. 트래픽이 불연속적으로 튀는 패턴, 즉 점심시간 폭주나 광고 발송 직후 같은 상황에 잘 맞는다.
세 번째는 Target Tracking Scaling, 목표값을 정해 두면 ASG가 알아서 그 값을 유지하도록 인스턴스 수를 조정하는 길이다. AWS 공식 문서가 "온도 조절기처럼 작동한다"고 비유하는 갈래다. 평균 CPU 50%를 목표로 두면, 60%로 올라갔을 때 ASG가 알아서 인스턴스를 추가하고, 30%까지 내려가면 알아서 제거한다. 임계값과 단계를 일일이 정의 안 해도 되니 가장 짧다. CloudWatch Alarm 두 개(스케일 아웃·스케일 인용)를 AWS가 자동으로 만들고 관리한다. AWS의 강한 권장이 이 정책이다. 신호가 용량에 반비례하는 메트릭(CPU, 요청 수 등)이라면 이게 거의 항상 정답이다.
네 번째는 Predictive Scaling, 과거 데이터(최대 14일치)를 기계 학습으로 분석해 미래 48시간 부하를 예측하고 미리 인스턴스를 띄워 두는 길이다. 2021년 5월에 EC2 Auto Scaling 기본 기능으로 합류했다. Target Tracking이 반응형(reactive)인 데 비해 Predictive는 예측형(proactive)이다. 매일 오전 9시에 트래픽이 두 배로 뛰는 패턴이 있으면, 8시 50분쯤 인스턴스가 이미 늘어나 있다. Cold start latency가 큰 앱(Java + Spring 부팅 3분 같은)에 효과가 크다. AWS 공식 문서가 명시하는 최소 데이터 요건은 24시간이고, 그 미만이면 예측 자체가 시작되지 않는다. 데이터가 늘수록 예측이 좋아져 14일치쯤 모이면 안정적인 패턴을 잡는다. 그리고 패턴이 주기적이어야 잘 맞는다. 매일 출퇴근 트래픽 같은 건 잘 맞고, 광고 캠페인처럼 한 번 터지고 마는 부하는 못 잡는다.
| 정책 | 입력 | 반응 시간 | 운영 복잡도 | 권장 상황 |
|---|---|---|---|---|
| Simple | 알람 1개 → 동작 1개 | 알람 후 300초+ | 매우 낮음 | 거의 권장 안 됨 |
| Step | 알람 + 단계 매트릭스 | 알람 후 즉시 | 중간 | 부하가 단계적으로 튀는 경우 |
| Target Tracking | 목표값(예: CPU 50%) | 즉시, AWS 자동 알람 | 가장 낮음 | 메트릭이 용량과 반비례하는 경우(CPU·요청 수) |
| Predictive | 14일치 학습 + 48시간 예측 | 사전(부하 도착 5~10분 전) | 낮음, 단 데이터 필요 | 주기적 패턴 + cold start가 긴 앱 |
여기서 어느 정책이 디폴트냐는 질문에 한 줄로 답하면, Target Tracking 하나에 CPU 50% 목표만 걸어 두면 90% 케이스를 처리한다. Step Scaling은 Target Tracking으로 안 잡히는 단계적 트래픽에서 추가로 얹는 보강이고, Predictive Scaling은 충분한 과거 데이터가 있는 안정 운영 단계에서 얹는 추가다. Simple Scaling은 새 ASG를 만들 때 굳이 처음부터 고를 일이 드물다.
ASG와 Spot: Mixed Instances Policy로 비용 깎기
ASG가 Spot, On-Demand, Reserved: 세 가지 가격 모델을 받아들이면 청구서를 절반 이하로 깎을 수 있다. 어떻게 합치냐가 Mixed Instances Policy다. 한 ASG 안에서 여러 인스턴스 타입과 On-Demand + Spot 비율을 동시에 정의할 수 있는 구조다. AWS가 2020년 11월에 한 ASG가 여러 Launch Template까지 받을 수 있도록 확장했다.
전형적인 구성은 이렇다. On-Demand 비율을 30%로 두고, 나머지 70%를 Spot으로 채운다. 인스턴스 타입은 m5.large·m5a.large·m5n.large·m6i.large 네 가지를 후보로 두고, allocation strategy를 price-capacity-optimized(2022-11 출시)로 설정한다. AWS가 새 ASG에 강하게 권장하는 전략이 이 price-capacity-optimized다. 다만 CLI/API의 기본값 자체는 여전히 lowest-price라서, 자동화 스크립트로 ASG를 만들면 명시적으로 지정해 줘야 새 권장 전략을 적용한다. 그러면 ASG가 시점마다 가장 싸면서 회수 위험이 낮은 풀에서 Spot을 가져온다. 4종류 풀에 분산돼 있으니 한 풀이 회수돼도 나머지 풀에서 채워 준다.
여기서 수치가 어떻게 나오는지 한 사례를 풀어 보자. m5.large On-Demand 시간당 약 $0.10, Spot은 시점마다 다르지만 같은 인스턴스가 보통 60~70% 할인된 값에 머문다(글 작성 시점에는 $0.034 정도다). Spot은 변동 가격이라 현재 값은 Spot pricing history로 확인해야 한다. 24시간×30일 = 720시간을 6대 운용한다고 치면, 전부 On-Demand 기준이 약 $432. On-Demand 30% + Spot 70% Mixed로 가면 같은 시간에 $0.10×6×0.3×720 + $0.034×6×0.7×720 = $130 + $103 = $233. 절반 가까이 줄어든다. 줄어드는 대신, 2분 통보가 평균 한 달에 몇 번 올 수 있고, 그때마다 새 인스턴스가 다른 풀에서 일어나야 하니 짧은 capacity 흔들림이 정상이다.
ASG와 Spot이 합쳐질 때 도움 되는 짝꿍이 Capacity Rebalancing이다. AWS가 2020년 11월에 출시한 기능이다. EC2가 "이 Spot 곧 회수될 가능성 높음"이라는 재조정 권고(rebalance recommendation)를 보내는데, ASG가 그 권고를 받자마자 새 인스턴스를 미리 띄우고 그게 InService가 된 다음에야 옛 인스턴스를 회수에 내준다. 2분 통보 후 강제 종료가 아니라, 그 전에 미리 교체하는 셈이다. 트래픽 손실 폭이 매우 작다.
여기서 언제 Mixed Instances + Spot을 쓰지 말아야 하는가를 한 번 짚고 싶다. 상태가 있는(stateful) 워크로드는 맞지 않다. 데이터베이스, 세션을 메모리에 보관하는 앱, 길게 도는 배치(처리 시간 30분 이상)에 해당한다. Spot 회수 2분이 그런 워크로드에서는 데이터 유실이나 재처리 비용 폭증을 부른다. ASG의 fault tolerance가 없는 곳에서 Spot은 도구가 아니라 사고의 출발점이다.
인스턴스 교체: Instance Refresh와 Lifecycle Hook

ASG에 새 AMI를 적용하거나 Launch Template을 업데이트하고 싶을 때, 기존에 떠 있는 인스턴스를 어떻게 새 설정으로 갈아치우느냐가 운영의 핵심 손잡이 한 갈래다. 콘솔에서 손으로 한 대씩 죽이며 새로 띄우는 건 5대만 돼도 30분이 걸리고, 100대면 사람 손으로 감당하기 어렵다.
Instance Refresh는 AWS가 2020년 6월에 출시한 기능이다. ASG에 "이 새 Launch Template 버전으로 갈아치워 줘"라고 명령하면, ASG가 minimum healthy percentage(기본 90%)를 지키면서 한 묶음씩(보통 10%) 새 인스턴스로 교체한다. 90%가 살아 있는 동안만 한 묶음을 죽이고 새로 띄우니, 트래픽 손실 없이 완전 교체가 가능하다. 100대 ASG면 한 번에 10대씩, 새 10대가 InService가 된 다음에야 다음 10대를 죽이는 식이다. 평균 30~45분에 끝난다.
Skip Matching이라는 옵션이 같이 있다. 이미 새 Launch Template과 같은 설정으로 도는 인스턴스는 교체 안 한다. 일부 인스턴스만 옛 버전인 경우 이 옵션을 켜면 옛 버전만 골라서 갈아준다. 다만 Attribute-Based Instance Type Selection을 쓰는 ASG에서는 Skip Matching을 지원하지 않는다. 인스턴스 타입을 속성(vCPU 4개·메모리 16GiB 같은)으로 지정하는 모드라 "같은 설정"의 정의가 모호하기 때문이다.
Lifecycle Hook은 인스턴스가 Pending 또는 Terminating 상태로 진입할 때 ASG가 그 진행을 일시 정지하고, 사용자에게 "이제 마무리 작업 시간을 줄게"라고 신호를 주는 메커니즘이다. 보통 Terminating에 훅을 걸어 둔다. 인스턴스가 죽기 직전에 메모리에 든 캐시를 디스크로 내려쓰거나, 진행 중인 요청을 다 처리하고, 모니터링 시스템에서 자기 자신을 빼는 등의 마무리 작업을 둔다. 훅이 끝났다는 신호(CompleteLifecycleAction)를 ASG에게 보내야 비로소 인스턴스가 진짜 죽는다. 시간 제한이 있고, 단일 훅의 HeartbeatTimeout이 기본 1시간이고, 전체 글로벌 타임아웃은 48시간 또는 HeartbeatTimeout × 100 중 더 작은 값이다. 시간 안에 신호를 못 보내면 ASG가 강제로 진행한다.
내가 Lifecycle Hook을 처음 잡아 본 사례는 이렇다. Spot 인스턴스 회수 2분 통보를 받았을 때 진행 중인 작업이 있으면 SQS DLQ로 메시지를 옮긴 다음 죽기로 했다. 훅을 Terminating:Wait에 걸어 두고, EventBridge로 회수 통보를 받으면 람다가 인스턴스에 SSM 명령을 보내 작업을 정리하게 했다. 정리가 끝나면 람다가 CompleteLifecycleAction을 보내고, ASG가 인스턴스를 진짜 종료한다. 이 훅이 없었다면 그 인스턴스의 메모리 큐에 남아 있던 메시지가 손실됐을 것이다.
여기서 한 단계 더 들어간 도구가 Warm Pools다. AWS가 2021년 4월에 출시했다. 평균 부팅 시간이 긴 앱(예: Windows 인스턴스 6분, 큰 컨테이너 이미지 다운로드 5분)에서, ASG가 인스턴스를 Stopped 상태로 미리 만들어 두고 풀에 보관해 두는 기능이다. 스케일 아웃 신호가 오면 ASG가 그 풀에서 인스턴스를 꺼내 시작(start) 시키고, 풀에서 새로 만들어 채운다. Stop된 인스턴스는 EBS 비용만 내니 EC2 시간 요금이 안 나가고, 시작은 부팅보다 30~60초 빠르다. 단점이 있다. 2025년 11월까지는 Mixed Instances Policy와 같이 못 썼다. 그 제약이 풀린 게 작년 말이다. Warm Pools는 cold start가 운영 SLA의 가장 큰 변수인 곳에서 의미 있다. 부팅 30초짜리 가벼운 컨테이너 워크로드라면 굳이 Warm Pools를 안 두는 게 더 단순하다.
어디까지 ASG가 답이고, 어디서부터 다른 길인가
ASG가 만능은 아니다. 두 갈래로 ASG가 과한 경우가 있다.
첫째, 상태가 인스턴스에 남는 워크로드. 데이터베이스, 세션 메모리, 로컬 파일 시스템에 진행 데이터를 쌓는 앱. ASG의 가치는 인스턴스를 자유롭게 죽이고 새로 띄우는 데서 오는 fault tolerance인데, 죽이면 안 되는 인스턴스를 ASG에 넣으면 그 가치를 잃는다. 이런 워크로드는 EC2란 무엇인가: 가상 머신의 추상 계층에서 짚은 RDS 같은 관리형 데이터베이스, ElastiCache, EFS 같은 상태 외부화 도구로 옮긴 다음에야 ASG 위에 얹는다.
둘째, 컨테이너 워크로드. ECS Service Auto Scaling이나 EKS Cluster Autoscaler/Karpenter가 컨테이너 단위로 같은 일을 더 정확하게 한다. ASG로 EC2 노드 한 묶음을 굴리고 그 위에서 ECS Task가 별도로 자동 조절되면, 두 단계 자동 조절이 서로 신호를 어긋내서 오히려 안 줄어드는 경우가 자주 생긴다. 컨테이너 위주 운영이면 ASG는 노드 풀의 무대만 만들고, 실제 자동 조절은 컨테이너 오케스트레이터가 들고 가는 게 더 단순하다.
셋째는 한 가지 더 있다. 부하가 아주 예측 가능한 안정 운영. 24시간 거의 같은 부하를 굴리는 백오피스 시스템 같은 곳. 이런 경우는 ASG로 자동 조절을 두는 비용보다 Spot, On-Demand, Reserved: 세 가지 가격 모델에서 본 Reserved Instance나 Compute Savings Plans로 약정 단가를 깎는 게 훨씬 큰 절약이다. ASG는 두되 Min=Desired=Max로 묶어 두는 패턴도 가능하다. 단순히 "여러 가용영역에 분산 + 자동 교체"를 위해 ASG의 일부 가치만 빌리는 방식이다.
ASG를 늘리고 줄이는 동적 조절기로 볼지, 아니면 AZ 분산 + Health 기반 교체만 빌리는 안전망으로 볼지는 이 서비스가 어떤 부하 위에서 도는지에 따라 다르다. 둘 다 정답이고, 둘이 한 시스템 안에 같이 있을 수도 있다(공통 인프라는 약정 + 안전망 ASG, 트래픽 폭주 영역은 동적 ASG).
운영 첫 달에 한 줄 적어 두면 다음 달이 편한 항목이 있다. Min·Max·Desired 세 숫자, Health Check Grace Period 한 숫자, Cooldown/Warmup 한 숫자. 이 다섯 숫자만 같이 본다. 이 다섯이 청구서에 가장 빠르게 새는 갈래이고, 잘 잡혀 있으면 ASG가 자기 일을 조용히 한다.
참고 자료
- Amazon EC2 Auto Scaling User Guide (Auto Scaling groups): ASG 공식 user guide, 모든 기능의 상세 설명
- Launch Configurations deprecation announcement: 2024-10-01 신규 계정 생성 차단 공지
- Health checks for instances in an Auto Scaling group: EC2 status check vs ELB health check 차이
- Scaling cooldowns for Amazon EC2 Auto Scaling: Cooldown vs Warmup 차이
- Capacity Rebalancing announcement: 2020-11 출시
- Predictive Scaling for EC2 Auto Scaling: Native EC2 Auto Scaling Predictive Scaling 출시 (2021-05)
- Warm Pools for EC2 Auto Scaling: 2021-04 출시
- Instance Refresh (Skip Matching): Skip Matching 동작과 ABS 비호환









