워크로드 성격별 CAST AI Workload Autoscaler Scaling Policy 추천 설정

Written in

by

대상 독자: Kubernetes를 운영하는 플랫폼/인프라 엔지니어
범위: CAST AI Workload Autoscaler(WOOP)의 Scaling Policy 상세 설정을 다룹니다. 노드 오토스케일링과 Evictor는 다루지 않으며, Kubernetes 네이티브 설정(PDB, probe 등)은 보조 팁으로만 언급합니다.

시작하기 전에: 시스템 정책과 자동 배정

Workload Autoscaler를 켜면 CAST AI가 모든 워크로드를 자동으로 프로파일링해서, 라벨과 워크로드 타입, 네임스페이스를 기준으로 성격에 맞는 시스템 정책에 자동 배정합니다. 별도 설정 없이도 온보딩 직후부터 기본적인 최적화가 시작되는 구조입니다.

시스템 정책은 다음과 같습니다.

시스템 정책 성격 기본 배정 대상
balanced 비용과 안정성의 균형. 가장 무난한 기본값 StatefulSet을 제외한 모든 워크로드의 기본
resiliency 안정성 최우선, 변경에 보수적 StatefulSet의 기본
stability 일관된 성능 유지, 중단 최소화 성능 민감 워크로드
cost-savings 비용 절감 극대화 절감 우선 워크로드
burstable 필요할 때 리소스를 늘렸다가 회수. look-back 7일 Job, CronJob 등 Job성 워크로드
readonly CAST AI 자체 컴포넌트 전용. 수정 불가 castware

즉, 아무것도 하지 않아도 워크로드 성격에 맞는 정책이 어느 정도는 알아서 매칭됩니다. 그렇다면 이 글은 왜 필요할까요?

시스템 정책은 “무난한 출발점”이지 “우리 워크로드에 맞는 정답”이 아니기 때문입니다. 시스템 정책은 직접 수정할 수 없고(복제만 가능), JVM의 기동 스파이크, 주기적 트래픽의 look-back 요구, 배치 job의 confidence 문제 같은 세부 특성까지 반영하지는 못합니다. 그래서 이 글에서 다루는 상세한 케이스의 워크로드들은 Create Scaling Policy로 Custom Policy를 만들어 운영하는 것을 권고합니다. 시스템 정책으로 시작하되, 워크로드 성격이 뚜렷한 그룹부터 하나씩 커스텀 정책으로 분리해 나가는 흐름이 이 글의 뼈대입니다.

이제 워크로드를 8가지 성격으로 분류하고, 각 분류가 왜 그런 스케일링 특성을 갖는지부터 짚은 뒤, 그에 맞는 Custom Policy 추천값을 제시하고, Terraform과 API로 코드화하는 방법까지 다룹니다.


0. 먼저 알아야 할 Scaling Policy 설정 항목

기본값은 시스템 정책마다 다릅니다. 아래 표에서는 balanced 정책 기준 값만 표기하고, 나머지는 “정책마다 다름”으로 적었습니다.

설정 항목 의미 기본값
Optimization 모드 콘솔에서 Read-only(추천만 생성) / Vertical(적용)로 설정. API/Terraform에서는 각각 READ_ONLY / MANAGED 값에 해당 정책마다 다름
applyType IMMEDIATE(즉시 적용, 파드 재시작/in-place) / DEFERRED(자연 재시작 시 적용, K8s 1.33+는 in-place resize 시도) balanced: IMMEDIATE
Recommendation percentile 목표 사용량 percentile (QUANTILE+p값 / MAX), CPU와 메모리 별도 balanced: CPU p80, Memory max
Overhead 추천값 위에 얹는 버퍼 balanced: CPU 10%, Memory 10%
Change sensitivity 추천이 기존 request와 얼마나 달라야 적용하는지 (PERCENTAGE 고정 / DEFAULT_ADAPTIVE 동적) 정책마다 다름 (Dynamic 권장)
Look-back period 추천 계산용 과거 데이터 창 (3시간~7일, CPU/메모리 별도) balanced: 3일 (burstable은 7일)
Constraints (min/max) 스케일 상한과 하한 (절대값 또는 원본 request 대비 %) 없음
Limits 처리 제거 / 유지 / 비율 유지 / 배수(multiplier) Memory 자동 1.5배
Startup metrics 기동 구간 처리: 포함 / startup recommendations(기동엔 원본 request, 이후 최적값) / 무시 시스템 정책: 기동 2분 무시
Confidence 자동 적용에 필요한 최소 데이터 신뢰도 90%
Rollout behavior Zero-downtime(단일 replica 임시 2개), one-by-one 순차 evict, eviction delay opt-in
Stall detection PSI 기반 CPU 압박 감지 시 추천 상향 (stall %, pod %) 정책마다 다름 (balanced: 10%)
Memory event OOM 등 메모리 이벤트 시 적용 방식 정책마다 다름
Downscaling 다운스케일만 별도 apply type 지정 정책마다 다름
JVM optimization JVM heap 메트릭 기반 메모리 추천 off
Excluded containers 최적화 제외 컨테이너 (예: sidecar) 없음
Horizontal autoscaling 정책 수준 네이티브 HPA(v2) 생성과 관리: replica 범위, 트리거, behavior, take-ownership off
Assignment rules namespace / GVK / label 표현식으로 자동 배정 정책마다 다름

중요한 상호작용 두 가지:

  1. 정책의 Horizontal autoscaling은 환경에 자체 HPA가 없을 경우에만 켜는 것을 권고합니다. Vertical과 Horizontal을 함께 켜면 CAST AI가 수직과 수평의 균형을 자동으로 잡아주지만(이때 CPU overhead 설정은 무시됩니다), 이미 팀에서 관리하는 HPA나 KEDA가 있다면 관리 주체가 둘이 되어 혼선이 생깁니다.
  2. 자체 HPA(직접 생성한 것, KEDA 등)가 있는 워크로드에 vertical 최적화를 적용하는 경우는 걱정하지 않아도 됩니다. Workload Autoscaler가 기존 HPA와의 관계를 인지하고 협조적으로 동작하도록 설계되어 있어, 자체 HPA는 그대로 두고 vertical만 켜면 알아서 잘 움직입니다.

운영 원칙 두 가지:

  • 새 커스텀 정책은 항상 Read-only로 시작해서 추천값을 1~2주 관찰한 뒤 Vertical로 설정하는 것을 권고합니다.
  • Zero-downtime updates는 StatefulSet을 제외한 모든 정책에서 켜는 것을 권고합니다. 대부분의 고객 환경에는 단일 replica Deployment가 생각보다 많고, 이 설정이 없으면 IMMEDIATE 적용 시 그대로 다운타임이 됩니다. in-place update가 동작하는 K8s 1.33+ 환경에서도 in-place가 불가능한 변경 상황이 있으므로 반드시 켜 두는 것을 권고합니다.

참고로 ArgoCD 같은 자동 동기화 CD가 구성되어 있어도 걱정할 필요가 없습니다. Workload Autoscaler가 적용하는 리소스 값은 Deployment 매니페스트를 고치는 것이 아니라 Webhook을 통해 Pod 생성 시점에 주입되는 값이기 때문에, GitOps 동기화(diff/self-heal)와 충돌할 위험이 없습니다.


1. Stateless 서비스 – web front / API / 모바일 백엔드 / BFF / gRPC

워크로드 특성

Stateless 서비스는 파드에 로컬 상태를 남기지 않습니다. 요청 처리에 필요한 데이터는 요청 안에서 생겼다가 사라지고, 세션이나 캐시 같은 상태는 외부 저장소(Redis, DB)에 맡깁니다. 이 구조는 스케일링 관점에서 좋은 케이스입니다. 이유는 세 가지입니다.

첫째, 재시작이 쉽습니다. 어떤 파드가 죽어도 다른 replica가 즉시 요청을 이어받고, 새 파드도 금방 트래픽을 받을 수 있습니다. WOOP가 추천값을 적용하려고 파드를 재시작해도 서비스 영향이 거의 없다는 뜻이고, 그래서 IMMEDIATE를 쓸 수 있습니다.

둘째, 리소스 사용량이 트래픽을 그대로 따라갑니다. CPU는 RPS에 비례해 오르내리고, 메모리는 커넥션 수와 버퍼 크기에 따라 비교적 안정적인 수준을 유지합니다. 과거 사용량이 곧 미래 예측이 되는, percentile 기반 추천이 가장 잘 맞는 프로필입니다.

셋째, 수평 확장이 잘 통합니다. replica를 늘리면 로드밸런서가 트래픽을 나눠주므로, 수직(request 최적화)과 수평(replica 조절)을 함께 돌릴 수 있는 유일한 분류이기도 합니다.

다만 하위 유형별로 주의할 점이 있습니다. gRPC는 HTTP/2 커넥션을 재사용하기 때문에 L4 로드밸런싱 환경에서는 새 파드로 트래픽이 퍼지지 않는 커넥션 불균형이 생길 수 있습니다. 수평 확장을 해도 기존 파드만 바쁜 상황이 나올 수 있어 L7 밸런싱이나 클라이언트 사이드 LB가 전제되어야 합니다. WebSocket 같은 실시간 서비스는 커넥션이 수 분에서 수 시간까지 유지되므로, 파드를 내리는 순간 대량 재연결이 몰립니다. “재시작이 쉽다”는 전제가 약해지는 지점이라 rollout을 천천히 가져가야 합니다.

추천 설정

설정 추천값 이유
applyType IMMEDIATE 재시작 부담이 적어 빠른 최적화 우선
Zero-downtime updates 켜기 단일 replica Deployment 보호. 1.33+에서도 필수
CPU percentile QUANTILE p80 순간 피크는 burst로 흡수
Memory MAX + overhead 10~15% 비압축성 리소스이므로 보수적으로
Sensitivity DEFAULT_ADAPTIVE 크기가 제각각인 서비스 묶음에 적합
Look-back 1~2일 일 단위 패턴 반영 + 빠른 반응
Rollout one-by-one + eviction delay 10~60초 gRPC/WebSocket처럼 커넥션이 긴 서비스는 delay를 늘려 drain 시간 확보
Horizontal 자체 HPA가 없는 환경이라면: min 2, CPU 70~80% 트리거, scale-down stabilization 300초 가용성 하한 + 플래핑 방지
CPU limit Remove limits throttling 방지

K8s 보조 팁: terminationGracePeriodSeconds와 preStop hook으로 커넥션 drain을 보장하고 PDB minAvailable까지 설정하면 더 나은 서비스 품질을 유지할 수 있습니다.

2. 트래픽 변동이 심한 서비스 – spike성 / 주기적 몰림

워크로드 특성

이 분류의 본질은 평균과 피크의 차이가 크다는 것입니다. 하루 평균 CPU가 200m인 서비스가 점심시간에는 2000m를 쓴다면, 평균 근처로 rightsizing하는 순간 피크 때마다 장애가 납니다. 오토스케일링 도구 입장에서 가장 다루기 까다로운 프로필인데, 변동이 예측 가능한가에 따라 전략이 갈립니다.

주기적 패턴(출퇴근, 점심, 주말, 월말 정산)은 시계열로 보면 규칙적입니다. 문제는 look-back 창이 주기보다 짧을 때 생깁니다. 예를 들어 look-back이 24시간인데 주말에 트래픽이 1/10로 떨어지는 서비스라면, WOOP는 일요일 저녁에 “이 워크로드는 리소스를 거의 안 쓴다”고 학습해서 request를 확 줄입니다. 그리고 월요일 오전 9시에 터집니다. look-back이 피크 주기 전체를 덮어야 한다는 원칙이 여기서 나옵니다. 주기가 규칙적이라는 점은 반대로 기회이기도 합니다. Predictive scaling이 패턴을 학습해 수요가 오기 전에 미리 리소스를 올려줄 수 있기 때문입니다.

예측 불가 spike(이벤트 오픈, 티켓팅, 뉴스 유입)는 과거 데이터가 미래를 말해주지 않는 유형입니다. 반응형 스케일링의 지연 사슬(메트릭 수집 => HPA 판단 => 파드 기동 => 필요 시 노드 프로비저닝)이 수 분 걸리는 동안 spike는 이미 지나가거나 장애를 냅니다. 따라서 이 유형에서 WOOP의 역할은 “최적화”가 아니라 평시 rightsizing이 spike 대비 여력을 깎아먹지 않게 방어하는 것입니다. 저트래픽 히스토리를 보고 request를 줄이려는 압력을, percentile 상향과 min constraint로 막는 방향입니다.

2-a. 주기적 패턴 – 추천 설정

설정 추천값 이유
Look-back 7일 주중/주말 사이클을 포함해야 다운스케일 후 월요일에 터지지 않음
Predictive scaling (CPU) enabled 주기 패턴을 학습해 수요 전 선제 상향
CPU percentile p90~p95 주기 피크 흡수
Zero-downtime updates 켜기 단일 replica 보호

2-b. 예측 불가 spike – 추천 설정

설정 추천값 이유
Percentile p99~MAX 평균 기반 추천은 spike에 무의미
Memory overhead 20~30% 첫 spike에서의 OOM 방지
Predictive scaling (CPU) enabled spike에 섞여 있는 반복 패턴까지 선제 대응
Constraints min 넉넉한 절대값 (또는 원본 request 대비 90~100%) 저트래픽 구간을 보고 과도하게 줄이는 것을 하한으로 차단
Stall detection stall threshold 10% 숨은 CPU 경합 조기 감지
Zero-downtime updates 켜기 단일 replica 보호
Horizontal 자체 HPA가 없다면: max replicas 여유, scale-up stabilization 0초 수평 확장이 spike의 주 방어선

K8s 보조 팁: spike 대응의 본체는 노드 headroom과 HPA behavior입니다. WOOP는 평시 rightsizing이 spike 대응력을 깎지 않게 지켜주는 역할이라고 보면 됩니다.

3. JVM, 웜업이 필요한 서비스 – Java / .NET / 캐시 웜업

워크로드 특성

JVM 워크로드는 오토스케일러를 두 번 속입니다.

첫 번째는 기동 구간입니다. 클래스 로딩, 스프링 컨텍스트 초기화, JIT 컴파일이 몰리는 기동 후 수 분간 CPU 사용량이 정상 상태의 2~5배까지 치솟습니다. 이 스파이크가 사용량 히스토리에 섞이면 percentile 기반 추천값이 부풀어서, 실제 정상 상태보다 훨씬 큰 request가 유지되고 비용이 낭비됩니다. 반대로 기동 메트릭을 무시하고 정상 상태 기준으로 줄이면, 이번엔 기동이 CPU 부족으로 한없이 느려지고 readiness 지연과 롤링 배포 정체로 이어집니다. 기동엔 넉넉하게, 정상 상태엔 타이트하게. 두 국면을 분리하는 것이 유일한 정답이고, startup recommendations가 존재하는 이유입니다.

두 번째는 메모리입니다. JVM은 -Xmx로 지정된 heap을 미리 확보하고 GC가 그 안에서 메모리를 관리합니다. 컨테이너 밖에서 보이는 메모리 사용량은 “JVM이 OS에서 가져간 양”이지 “애플리케이션이 실제로 쓰는 양”이 아닙니다. heap을 4Gi로 잡아두면 실제 객체가 500Mi든 3Gi든 컨테이너 메트릭은 비슷하게 보이므로, 컨테이너 메트릭 기반 rightsizing은 JVM에서 구조적으로 부정확합니다. CAST AI의 JVM optimization이 컨테이너 메모리 대신 실제 heap 사용 메트릭을 보는 이유입니다. 치명적인 제약도 하나 있습니다. request가 heap + metaspace + off-heap(네이티브 버퍼, 스레드 스택)보다 낮게 스케일되면 JVM은 서서히 줄어드는 것이 아니라 즉시 OOMKilled 됩니다. min constraint가 선택이 아니라 필수인 이유입니다.

.NET도 같은 구조(JIT + GC + 관리 heap)라 동일하게 적용되고, 캐시 웜업 후에야 정상 성능이 나오는 서비스도 기동 국면 분리라는 점에서 같은 정책으로 묶을 수 있습니다.

추천 설정

설정 추천값 이유
Startup metrics Startup recommendations 모드, period 5~10분 기동엔 원본 CPU request 적용 => 이후 최적값 전환. 기동 스파이크의 추천 부풀림과 HPA 오탐을 동시에 해결
JVM 메트릭 수집 + Auto instrument 둘 다 켜기 Custom metric의 JVM 메트릭 수집과 Auto instrument를 함께 켜면 실제 heap 사용량 기반으로 추천이 계산됩니다. JVM에서 효과가 가장 큰 설정
Memory MAX + overhead 15~20% heap + metaspace + off-heap 변동 흡수
Constraints min (memory) -Xmx + off-heap을 감당하는 절대값 heap보다 낮게 스케일되면 즉사하는 것을 막는 하드 가드레일
Zero-downtime updates 켜기 단일 replica 보호
Rollout IMMEDIATE + one-by-one + eviction delay 60초 이상 웜업이 느리므로 동시 재시작 금지

⚠️ 주의: JVM 메트릭 수집과 Auto instrument를 켜기 전에 반드시 확인할 것. 이 기능은 JMX 기반으로 JVM 메트릭을 노출하고 수집합니다. Datadog처럼 -jmx 입력을 사용하는 다른 APM/모니터링 에이전트가 이미 붙어 있는 환경이라면 JMX 포트가 충돌할 수 있습니다. 적용 전에 기존 에이전트의 JMX 포트 구성을 확인하고, 충돌 시 포트를 분리해서 사용하기를 권고합니다.

K8s 보조 팁: startupProbe로 웜업 완료 전 트래픽을 차단하기를 권고합니다. -XX:MaxRAMPercentage를 쓰는 경우 WOOP의 request 변경에 따라 heap도 함께 움직이는지 확인이 필요합니다.

4. Stateful, 데이터 워크로드 – Elasticsearch / Redis / Kafka / in-cluster DB

워크로드 특성

이 분류는 앞의 셋과 정반대 성질을 갖습니다. 파드가 데이터를 들고 있고, 재시작과 재배치의 비용이 리소스 절감액보다 훨씬 비쌉니다.

무엇이 비싼지 구체적으로 보면 이렇습니다. Elasticsearch 데이터 노드가 재시작되면 해당 노드의 샤드가 unassigned 상태가 되고, 복구 과정에서 대량의 샤드 리밸런싱 트래픽이 발생합니다. 노드당 수백 GB를 들고 있다면 복구에 수십 분에서 수 시간이 걸리고, 그동안 클러스터는 yellow 상태로 버팁니다. Kafka 브로커가 내려가면 그 브로커가 리더였던 파티션들의 리더십이 이동하고, 복귀 후 ISR 재동기화가 따라옵니다. Redis가 primary 스토어라면 재시작은 곧 캐시 미스 폭풍이거나, persistence 설정에 따라 데이터 유실 리스크입니다. in-cluster RDBMS는 커넥션 드레인, 복제 재동기화, 최악의 경우 failover까지 연쇄됩니다.

게다가 이들은 PV에 묶여 있어 특정 AZ에 고정되고, 메모리 사용 패턴도 특이합니다. 데이터베이스류는 가용 메모리를 페이지 캐시와 버퍼 풀로 최대한 활용하도록 설계되어 있어서, “사용량이 높아 보이는 것”이 낭비가 아니라 의도된 동작입니다. 사용량 메트릭만 보고 줄이면 캐시 히트율이 떨어져 성능이 조용히 나빠집니다.

그래서 이 분류의 전략은 “빠른 최적화”가 아니라 무중단 관찰 후 선별 적용입니다. Read-only로 추천값을 계속 받아보면서 과다 프로비저닝 규모를 파악하고, 적용은 DEFERRED로 자연 재시작(버전 업그레이드, 유지보수) 타이밍에 얹거나 K8s 1.33+의 in-place resize에 맡깁니다. 유일한 예외는 휘발성 캐시 Redis입니다. 데이터가 날아가도 원본에서 다시 채우면 되는 캐시라면 사실상 stateless에 가깝게 다뤄도 됩니다. 같은 Redis라도 용도에 따라 정책이 갈려야 하는 이유입니다.

추천 설정

설정 추천값 이유
Optimization 모드 처음엔 Read-only 추천만 보고 수동 반영, 신뢰가 쌓인 뒤 일부만 Vertical로 설정
applyType DEFERRED 파드 재시작 없음. 1.33+면 in-place, 아니면 자연 재시작 시 적용
Memory MAX + overhead 20~35% 데이터 워크로드의 OOM은 곧 장애
CPU percentile p95 이상 압축, 리밸런싱 피크 반영
Sensitivity PERCENTAGE 20% 잦은 변경 차단
Look-back 7일 백업, 리밸런싱 등 주기 작업 포함
Anti-affinity consider_anti_affinity = true 재배치 시 제약 존중
Zero-downtime updates 켜지 않음 StatefulSet/PVC 워크로드 미지원 (이 분류만 예외)

Redis는 용도로 나누기를 권고합니다: 휘발성 캐시라면 1번에 가깝게 IMMEDIATE도 가능하고, primary 데이터스토어라면 이 분류대로 갑니다. label(cache-tier=volatile)로 assignment rule을 나누면 정책 두 개로 깔끔하게 분리됩니다.

K8s 보조 팁: PDB maxUnavailable: 1을 설정하고, Elasticsearch는 노드 role별로 정책을 분리(마스터=Read-only, 코디네이팅=완화)하면 더 안전하게 운영할 수 있습니다.

5. 배치, Job – CronJob / 빅데이터 / DB job / ML 학습 / CI Runner

워크로드 특성

배치는 수명 주기가 다릅니다. 서비스는 계속 떠 있으면서 부하가 변하는 존재지만, Job은 시작 => 처리 => 종료라는 유한한 생애를 삽니다. 이 차이가 오토스케일링의 전제 두 가지를 바꿉니다.

첫째, “재시작해서 새 request를 적용한다”는 개념이 성립하지 않습니다. 실행 중인 job을 죽이면 최적화가 아니라 작업 손실입니다. 대신 다음 실행이 곧 자연스러운 적용 시점입니다. 이번 실행에서 관찰한 사용량으로 추천을 만들고, 다음 스케줄에서 새 request로 뜨는 구조라 DEFERRED가 자연스럽습니다.

둘째, 리소스 사용이 국면(phase)을 탑니다. ETL job은 보통 데이터 로드(메모리 급증) => 변환(CPU 집중) => 쓰기(I/O 대기)처럼 단계별로 병목이 이동하고, 피크는 특정 국면에 몰립니다. 이 피크를 percentile로 깎으면 다음 실행의 해당 국면에서 OOM이 나고, Job 재시도 정책에 따라 OOM => 재실행 => 같은 지점에서 다시 OOM이라는 루프에 빠집니다. 배치의 메모리를 MAX 기준으로 잡아야 하는 이유이고, OOM이 나면 즉시 메모리를 올려 루프를 끊는 memory_event = IMMEDIATE가 중요한 이유입니다.

셋째, 메트릭 밀도가 낮습니다. 하루에 한 번 30분 도는 job은 하루 24시간 중 30분치 데이터만 남깁니다. confidence 기본값(90%)을 그대로 쓰면 “데이터가 부족하다”는 판정이 계속되어 최적화가 영영 시작되지 않을 수 있습니다. 실행 빈도가 낮은 job일수록 confidence를 낮추고 look-back을 주기에 맞춰 늘려야 합니다.

하위 유형별 온도차도 있습니다. CI Runner는 수 분짜리 수명에 실패해도 재실행 부담이 적은 편이라 가장 공격적으로 최적화할 수 있고, 빅데이터/ML 학습은 수 시간짜리 실행에 중간 손실이 커서 checkpoint 유무가 관건이며, DB job(마이그레이션, 백업)은 짧더라도 중단 시 데이터 정합성이 깨질 수 있어 4번 분류에 준하는 보호가 필요합니다.

추천 설정

설정 추천값 이유
applyType DEFERRED 다음 실행에서 적용, 실행 중인 job에 불간섭
Look-back job 주기 전체 커버 (일간=2일, 주간=7일) 피크가 창 밖으로 나가면 다음 실행에서 OOM
Memory MAX + overhead 20~35% 배치의 메모리 피크는 순간적이고 치명적
CPU percentile p95~max 배치는 CPU를 다 쓰는 것이 정상
Memory event IMMEDIATE OOM 시 즉시 상향으로 재실행 루프 차단
Confidence 70~80%로 완화 실행 빈도가 낮아 데이터 밀도가 낮음. 90%면 최적화가 영영 시작되지 않을 수 있음

DB job은 4번과 5번의 교집합입니다. DEFERRED에 넉넉한 memory min을 더하고, 중단 시 정합성이 깨질 수 있는 job이라면 Read-only로 두고 추천만 참고하기를 권고합니다.

6. GPU – 추론 / 학습

워크로드 특성

GPU 워크로드가 별도 분류인 이유는 세 가지입니다. 비용의 자릿수가 다르고(GPU 노드는 일반 노드의 수 배에서 수십 배), GPU는 나눠지지 않으며(time-slicing/MIG를 쓰지 않는 한 파드당 정수 개 할당), 노드 확보가 느립니다(GPU 인스턴스는 가용성이 낮아 프로비저닝에 수 분 이상).

여기서 중요한 포인트가 하나 있습니다. WOOP가 직접 최적화하는 것은 GPU가 아니라 GPU 파드의 CPU와 메모리 request입니다. GPU 파드도 전처리, 토크나이징, 데이터 로딩에 CPU와 메모리를 쓰는데, 이 값이 과하게 잡혀 있으면 비싼 GPU 노드의 남은 공간에 다른 워크로드를 채울 수 없습니다. GPU 노드에서 CPU/메모리 request를 정확하게 잡는 것은 일반 노드에서보다 절감 효과가 훨씬 큽니다. bin-packing 대상 노드 자체가 비싸기 때문입니다.

운영 형태는 둘로 갈립니다. 추론(inference)은 요청을 받아 응답하는 서빙이므로 성격상 stateless 서비스에 가깝지만, 기동 시 수 GB짜리 모델을 GPU 메모리에 로드하는 웜업 국면이 있습니다. 3번(JVM, 웜업)과 같은 구조라 startup recommendations의 period를 모델 로드 시간에 맞춥니다. 수평 확장은 CPU 사용률이 아니라 GPU 활용률이나 동시 요청 수 같은 커스텀 메트릭이 정확합니다. 학습(training)은 수 시간에서 수 일짜리 배치입니다. 재시작하면 마지막 checkpoint 이후의 연산이 전부 사라지므로, 재시작을 유발하는 IMMEDIATE는 금물이고 5번 배치 정책을 준용합니다.

추천 설정

  • 추론(serving): IMMEDIATE + Zero-downtime updates 켜기 + startup recommendations(period = 모델 로드 시간) + one-by-one. GPU 활용률 기반 수평 확장은 HPA/커스텀 메트릭의 몫입니다.
  • 학습(training): DEFERRED + look-back은 학습 사이클 커버 + memory MAX. checkpoint 손실 방지를 위해 IMMEDIATE는 사용하지 않기를 권고합니다.

7. 인프라, 시스템 컴포넌트 – DaemonSet / Istio / Argo / 모니터링

워크로드 특성

이 분류는 “스케일링 대상”이라기보다 클러스터의 다른 모든 워크로드에 오버헤드로 얹히는 컴포넌트들입니다. 개별 파드는 작아 보여도 곱해지는 수가 크다는 것이 공통점입니다.

DaemonSet은 replica 수를 조절할 수 없습니다. 노드 수가 곧 파드 수입니다. 따라서 수평 스케일링은 개념적으로 불가능하고 vertical만 유효합니다. 대신 영향력이 큽니다. DaemonSet request를 100m 줄이면 노드 수 x 100m이 통째로 돌아오고, 반대로 추천 변경 한 번이 전체 노드에서의 재시작을 의미하므로 변경 빈도를 최소화해야 합니다. 또 DaemonSet request는 모든 노드에서 선점되는 고정 오버헤드라서, 이 값이 부풀어 있으면 노드 bin-packing 효율 전체가 나빠집니다.

Istio sidecar(istio-proxy)는 곱셈이 파드 단위로 일어납니다. 메시에 들어간 모든 파드에 주입되므로, sidecar 기본 request(예: CPU 100m/Mem 128Mi)가 실제 사용량(수십 m)보다 크면 그 차액 x 전체 파드 수만큼 클러스터가 낭비됩니다. 반대로 sidecar를 최적화 대상에 포함하면 앱 컨테이너와 sidecar가 함께 조정되므로, 처음에는 excludedContainers로 빼고 관찰한 뒤 단계적으로 포함하는 접근을 권고합니다.

Argo Rollouts는 자체적으로 ReplicaSet을 조작하며 canary/blue-green을 진행하는 배포 컨트롤러입니다. WOOP의 재시작이 진행 중인 배포와 겹치면 분석(analysis) 단계가 오염될 수 있어, 배포가 잦은 환경에서는 DEFERRED로 두고 배포 자체를 적용 타이밍으로 쓰는 편이 깔끔합니다. Argo Workflows는 이름만 비슷할 뿐 성격은 배치이므로 5번 정책으로 배정합니다. 그리고 앞서 언급했듯이 ArgoCD 같은 자동 동기화 CD가 구성되어 있어도 문제없습니다. 추천값은 Deployment가 아니라 Webhook을 통해 Pod에 주입되는 값이라, GitOps 동기화 설정과 충돌할 위험이 없습니다.

모니터링 스택(Prometheus 등)은 메모리 사용량이 트래픽이 아니라 메트릭 카디널리티(시계열 수)에 비례해 계단식으로 늘어나는 특성이 있습니다. 새 서비스 배포나 라벨 추가로 시계열이 늘면 메모리가 한 단계 뛰고 내려오지 않으므로, MAX에 높은 overhead로 여유를 두는 편이 안전합니다.

추천 설정

  • DaemonSet: DEFERRED + memory MAX + sensitivity 20% (변경 한 번 = 노드 수만큼의 재시작). stall detection은 DaemonSet에 적용되지 않습니다(공식 제약).
  • Istio sidecar: 먼저 excludedContainers: [istio-proxy]로 시작(보수적)하고, 메트릭 확인 후 sidecar 포함(적극적)으로 전환하기를 권고합니다.
  • Argo Rollouts: one-by-one rollout이 공식 지원됩니다. 배포가 잦다면 DEFERRED를 권고합니다.
  • Argo Workflows: 5번 배치 정책으로 배정합니다.
  • Prometheus 등 모니터링: memory MAX + overhead 30% + DEFERRED. 한 가지 주의할 점은, K8s 1.33+ in-place resizing 환경에서는 recommendation-applied-at 어노테이션이 30분마다 갱신될 수 있어 어노테이션을 라벨로 수집하는 수집기의 카디널리티가 폭발할 수 있다는 것입니다. 수집 제외 설정을 권고합니다.

8. 이미 문제가 있는 워크로드 – OOM과 안티패턴

기존 문제도 Workload Autoscaler로 해결할 수 있습니다

여기 있는 항목들은 워크로드 종류가 아니라, 이미 클러스터에서 문제를 일으키고 있는 상태들입니다. 좋은 소식은 기존에 문제가 발생하고 있는 워크로드들도 Workload Autoscaler를 통해 문제를 해결할 수 있다는 점입니다. OOM이 반복되는 워크로드, 단일 replica라 재시작이 무서운 워크로드 같은 만성 질환들이 정책 설정 몇 가지로 관리 가능한 상태가 됩니다.

OOM의 역학부터 이해하면 좋습니다. 컨테이너는 memory limit을 넘는 순간 커널에 의해 즉사합니다. CPU처럼 throttling으로 버티는 것이 불가능합니다. rightsizing은 본질적으로 관찰된 사용량 근처까지 request를 내리는 행위이므로, 관찰 창(look-back)에 잡히지 않은 피크가 있으면 반드시 OOM으로 이어집니다. 방어는 3중으로 갑니다. ① look-back을 피크 주기 이상으로(관찰 누락 방지), ② overhead 20% 이상(미관찰 변동 흡수), ③ memory_event = IMMEDIATE(그래도 터지면 즉시 상향해 반복 차단).

한 가지 더 중요한 케이스가 있습니다. OOM의 원인이 메모리 누수라면 이야기가 달라집니다. 누수가 있는 워크로드는 memory_event가 OOM 때마다 추천값을 계속 올리게 되고, 그 결과 예상치 못하게 커다란 노드가 계속 생성될 수 있습니다. limit 설정만으로는 이 확산을 막지 못합니다. 이런 상황을 방지하려면 constraint memory max에 워크로드 특성상 절대 나올 수 없는 적절한 상한(예: 30Gi)을 명시적으로 설정하기를 권고합니다. 추천값이 상한에 붙어 있다면 그것이 곧 누수의 신호이기도 합니다.

체크리스트

  • OOM: memory_event = IMMEDIATE + memory limit Automatic(1.5배) + overhead 20% 이상. look-back은 항상 피크 주기 이상으로 설정합니다.
  • 메모리 누수 의심 워크로드: constraint memory max를 절대 도달할 수 없는 값(예: 30Gi)으로 명시해서, 누수가 무한정 큰 노드 생성으로 번지는 것을 차단합니다.
  • 단일 replica + PDB 없음: IMMEDIATE 재시작이 곧 다운타임입니다. Zero-downtime updates(임시 2 replica)를 켜기를 권고합니다. 단, Deployment 전용이며 PVC 미사용 조건입니다.
  • CPU limit 남발: throttling의 원인입니다. Remove limits가 기본기입니다.
  • 모든 워크로드를 한 정책에: percentile, look-back, applyType은 워크로드 성격의 함수입니다. 성격이 다른 워크로드는 정책을 분리하기를 권고합니다.

전체 요약 표

분류 applyType CPU Memory Look-back 핵심 스위치
1. Stateless IMMEDIATE p80 MAX +10% 1~2d zero-downtime, one-by-one
2-a. 주기 트래픽 IMMEDIATE p90~95 MAX 7d Predictive scaling
2-b. Spike IMMEDIATE p99~MAX MAX +20~30% 7d min constraint, predictive, stall detection
3. JVM, 웜업 IMMEDIATE p80 MAX +15~20% 2d JVM 메트릭 + Auto instrument, startup recs
4. Stateful DEFERRED p95 MAX +20~35% 7d Read-only 시작, sensitivity 20%
5. 배치, Job DEFERRED p95~MAX MAX +20~35% job 주기 memory_event IMMEDIATE, confidence 완화
6. GPU 추론 IMMEDIATE / 학습 DEFERRED 1, 3번 / 5번 준용 startup period = 모델 로딩
7. 인프라 DEFERRED p90 MAX +30% 7d excludedContainers(istio-proxy)
8. (횡단) 피크 주기 이상 constraint max(누수 방어), zero-downtime

Terraform으로 코드화하기

castai/castai provider의 castai_workload_scaling_policy 리소스로 정책 전체를 IaC로 관리할 수 있습니다. 대표 3개 정책 예시입니다.

# 1. Stateless 정책
resource "castai_workload_scaling_policy" "stateless" {
  name              = "stateless-services"
  cluster_id        = castai_eks_cluster.this.id
  apply_type        = "IMMEDIATE"
  management_option = "MANAGED" # 콘솔의 Vertical에 해당. 최초 도입 시엔 READ_ONLY(콘솔의 Read-only) 권고

  assignment_rules {
    rules {
      workload {
        gvk = ["Deployment"]
        labels_expressions {
          key      = "tier"
          operator = "In"
          values   = ["frontend", "api", "bff"]
        }
      }
    }
  }

  cpu {
    function                 = "QUANTILE"
    args                     = ["0.80"]
    overhead                 = 0.10
    look_back_period_seconds = 172800 # 2d
    apply_threshold_strategy { type = "DEFAULT_ADAPTIVE" }
  }

  memory {
    function                 = "MAX"
    overhead                 = 0.10
    look_back_period_seconds = 172800
    apply_threshold_strategy { type = "DEFAULT_ADAPTIVE" }
    limit {
      type       = "MULTIPLIER"
      multiplier = 1.5
    }
  }

  rollout_behavior { type = "NO_DISRUPTION" } # one-by-one 순차 evict

  confidence { threshold = 0.9 }
}

# 3. JVM 정책
resource "castai_workload_scaling_policy" "jvm" {
  name              = "jvm-services"
  cluster_id        = castai_eks_cluster.this.id
  apply_type        = "IMMEDIATE"
  management_option = "MANAGED"

  assignment_rules {
    rules {
      workload {
        gvk = ["Deployment", "StatefulSet"]
        labels_expressions {
          key      = "runtime"
          operator = "In"
          values   = ["jvm"]
        }
      }
    }
  }

  cpu {
    function                 = "QUANTILE"
    args                     = ["0.80"]
    overhead                 = 0.10
    look_back_period_seconds = 172800
    apply_threshold_strategy { type = "DEFAULT_ADAPTIVE" }
  }

  memory {
    function = "MAX"
    overhead = 0.20
    min      = 1.0 # GiB 하한 - heap + off-heap 가드레일
    apply_threshold_strategy { type = "DEFAULT_ADAPTIVE" }
  }

  # JVM heap 메트릭 기반 메모리 추천
  jvm {
    memory { optimization = true }
  }

  # 기동 구간: 스파이크가 추천값을 부풀리지 않게 처리
  startup {
    period_seconds = 420 # 7min
  }

  rollout_behavior { type = "NO_DISRUPTION" }
}

# 4. Stateful 정책
resource "castai_workload_scaling_policy" "stateful" {
  name              = "stateful-data"
  cluster_id        = castai_eks_cluster.this.id
  apply_type        = "DEFERRED"  # 파드 재시작 없음
  management_option = "READ_ONLY" # 추천만 생성(콘솔의 Read-only) - 검증 후 Vertical로 설정

  assignment_rules {
    rules {
      workload { gvk = ["StatefulSet"] }
    }
    rules {
      namespace { names = ["elasticsearch", "kafka", "redis-primary"] }
    }
  }

  cpu {
    function                 = "QUANTILE"
    args                     = ["0.95"]
    overhead                 = 0.15
    look_back_period_seconds = 604800 # 7d
    apply_threshold_strategy {
      type       = "PERCENTAGE"
      percentage = 0.2 # 변경 빈도 최소화
    }
  }

  memory {
    function                 = "MAX"
    overhead                 = 0.30
    look_back_period_seconds = 604800
    apply_threshold_strategy {
      type       = "PERCENTAGE"
      percentage = 0.2
    }
  }

  anti_affinity { consider_anti_affinity = true }
  downscaling   { apply_type = "DEFERRED" }
  memory_event  { apply_type = "IMMEDIATE" } # OOM 시에는 즉시 상향
}

정책 수준 네이티브 HPA 설정(hpa_settings: replica 범위, 트리거, behavior, take-ownership)과 GPU 관리 필드는 최신 provider에 추가되었습니다. 사용 전 provider 버전과 리소스 문서의 스키마를 확인하기를 권고합니다.

API로 만들기

엔드포인트: POST /v1/workload-autoscaling/clusters/{clusterId}/policies

curl --request POST 
  --url "https://api.cast.ai/v1/workload-autoscaling/clusters/${CLUSTER_ID}/policies" 
  --header "X-API-Key: ${CASTAI_API_KEY}" 
  --header "Content-Type: application/json" 
  --data '{
    "name": "spike-services",
    "applyType": "IMMEDIATE",
    "cpu":    { "target": "p99", "overhead": 0.20 },
    "memory": { "target": "max", "overhead": 0.30 },
    "assignmentRules": [
      {
        "workload": {
          "gvk": ["Deployment"],
          "labelsExpressions": [
            {
              "key": "traffic-pattern",
              "operator": "KUBERNETES_LABEL_SELECTOR_OP_IN",
              "values": ["spike", "event"]
            }
          ]
        }
      }
    ]
  }'

조회와 수정은 각각 GET .../policies, PUT .../policies/{policyId}를 사용합니다. 워크로드별 배정 경로는 workload 조회 API의 scalingPolicyOrigin(api/annotations/assignment-rules/default) 필드로 추적할 수 있습니다. 전체 스키마는 API 레퍼런스를 참고하기를 권고합니다.

개별 워크로드 예외는 어노테이션이 최우선입니다.

metadata:
  annotations:
    workloads.cast.ai/configuration: |
      scalingPolicyName: stateless-services
      vertical:
        optimization: on
        excludedContainers:
          - istio-proxy

마치며

핵심은 두 문장으로 요약됩니다.

  1. 시스템 정책은 출발점, 커스텀 정책이 목적지입니다. 자동 배정으로 시작하되, 성격이 뚜렷한 워크로드 그룹부터 Create Scaling Policy로 분리해 나가기를 권고합니다.
  2. look-back은 워크로드의 피크 주기보다 항상 길어야 합니다. 주기 트래픽 7일, 배치는 job 주기, 나머지는 1~2일이 기준입니다.

참고 자료

Tags

Categories

댓글 남기기

JJ's technical blog

A blog about the messy, fascinating intersection of Kubernetes and AI – where infrastructure decisions increasingly shape whether AI initiatives actually succeed. Drawing on hands-on experience with enterprise customers across Korea and APAC, this space covers everything from cluster optimization to the evolving demands AI workloads place on cloud-native systems.

Seoul,
South Korea

JJ's technical blog에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기