Skip to Content
Blog왜 트래픽은 받았지만 스케일링은 못 했을까?
  • #Istio
  • #오토스케일링
  • #메트릭
  • #트러블슈팅

왜 트래픽은 받았지만 스케일링은 못 했을까?

트래픽이 몰리는 날에는 보통 미리 Pod들을 준비해둡니다. 몰려올 부하를 충분히 버티기 위한 단계, pre-warming입니다. 하루는 예열이 충분하지 않았고, 트래픽이 들어온 순간 서비스가 요청을 처리 할 수 없었습니다. 예측보다 큰 부하가 오면 처리를 다 못할수도 있으니깐요.

그렇지만 트래픽은 계속 들어오고 있어도, 오토스케일링은 Pod를 충분히 늘리지 않았습니다. 서비스는 자동으로 회복하지 못했고, 결국 사람이 직접 개입을 한 뒤에야 정상화됐습니다.


오토스케일링이 본 숫자

오토스케일링은 상황을 직접 판단할 능력이 없습니다. 단순하게 지정한 지표를 보고, 그 숫자에 맞게 Pod수를 조절합니다.

당시 기준으로 쓰던 지표는 처리된 요청 수였습니다. 겉으로 보면 받은 요청 기준으로 트래픽 기반 스케일링이니 ‘잘 되는거같은데?’ 라는 생각을 하게되었었습니다. 맹점이 하나 있었는데, 문제는 그 트래픽을 어느 위치에서 센 값인가 였죠.

우리는 요청을 Pod가 처리한 요청수로 세고 있었습니다. 즉 서비스로 들어오려는 전체 요청이 아니라, 실제로 Pod까지 도달해 처리되기 시작한 요청 수에 가까운 값이었습니다.

요청이 Service를 지나 Pod에 도달하고, Pod가 처리하기 때문에 평소에는 문제가 없었습니다. 정상 상황은 대략 이렇습니다.

처리된 요청 수(모든 Pod 합) = 스케일 기준HPA · 오토스케일링정상: 처리된 요청 ≈ 들어온 요청 → 스케일 정상들어온 요청ServicePodPodPod

이때는 들어온 요청처리된 요청 사이에 큰 차이가 없습니다. 그래서 받은 쪽 기준 지표로 스케일링해도 문제가 드러나지 않습니다.


트래픽은 많이 받았는데, 스케일링은 되지 않은 이유

저희가 겪었던 장애 상황에서는 두 기준치에 따라 값이 달라집니다.

부하가 커져 Pod가 readiness check에 실패하면 Kubernetes는 그 Pod를 라우팅 대상에서 제외합니다.

이 동작 자체는 정상입니다. 준비되지 않은 Pod로 요청을 보내지 않기 위한 보호 장치이니깐요. 그치만, 여러 Pod가 readiness에 실패하기 시작하면 남은 Pod로 요청이 더 몰립니다. 남은 Pod들도 과부하가 되고, 다시 readiness에 실패합니다.

결국 라우팅 대상이 줄어들수록 실제로 처리되는 요청 수도 같이 줄어듭니다.

readiness 실패readiness 실패남은 Pod로 집중계속 들어오는 요청ServicePodPodPod

여기서 핵심은 트래픽이 줄어든 게 아니라는 점입니다. 지표상으로 놓쳤던 부분은 트래픽이 줄어든것이 아니라 들어온 요청은 계속 많았습니다. 다만 라우팅 대상이 되는 Pod는 점점 줄어들었고, 실제로 요청을 받을 수 있는 Pod에만 요청이 흘러갔습니다.

readiness 실패로 라우팅 대상이 아니게 된 Pod는 더 이상 요청을 받지 않습니다. 그러니 받은 쪽 기준의 요청 수 역시 남아 있는 Pod가 실제로 받아낸 요청만 집계하게 됩니다. 장애가 심해질수록 오토스케일링 지표도 같이 내려가는 구조였습니다.

결과적으로 시스템은 이런 루프에 빠졌습니다. 서비스를 회복시키려면 Pod를 늘려야 했지만, Pod를 늘리는 기준 지표상으로는 오히려 낮아지고 있었습니다.


어떤 값을 기준으로 스케일링해야 했을까?

해결은 스케일링 기준을 처리된 요청이 아니라 들어온 요청에 맞추는 것이었습니다.

실제로는 Istio request metric의 reporter 조건을 바꾸는 일이었습니다. Istio metric에는 같은 요청이라도 reporter 관점이 있습니다. destination은 요청을 받은 workload 기준이고, source는 요청을 보낸 쪽 기준입니다. 이 상황에서 필요했던 값은 destination이 실제로 처리한 양이 아니라, 서비스로 유입되는 수요였습니다.

같은 요청 → Service → Pod 흐름에서, 우리가 봤어야 했던 두 값은 서로 다른 지점에 있습니다.

실제 요청량probe 실패probe 실패처리된 요청량요청ServicePodPodPod
  • 실제 요청량 — 서비스로 들어오는 요청 전체입니다. probe 실패와 무관하게 그대로입니다.
  • 처리된 요청량 — 라우팅 대상으로 남아 있는 Pod가 실제로 처리해낸 양입니다. probe 실패로 연결이 끊긴 Pod로 향하던 요청은 여기에 잡히지 않습니다.

probe 실패로 끊긴 Pod가 늘어날수록 처리된 요청량은 낮아지지만, 실제 요청량은 줄지 않습니다. 우리가 스케일링 기준으로 봤어야 했던 건 실제 요청량이었습니다.

실제 장애 당시 두 값을 같이 살펴보면, 이 차이가 그대로 드러납니다. source(들어온 요청)는 최대 612 rps까지 치솟는데 destination(처리된 요청)은 32 rps까지 무너집니다.

위 그림의 실제 요청량(source) 처리된 요청량(destination) 을 실제 메트릭으로 다시 그린 그래프입니다.

처리해야될 실제 요청 source처리한 실제 요청 destination스케일링이 놓친 수요
010020030040050060015:5516:0016:1016:2016:3016:4016:50

그래서 스케일링 쿼리의 reporter 조건을 바꿨습니다.

- reporter="destination" # 받은 쪽 기준: 실제 도달해 처리되기 시작한 요청 + reporter="source" # 보낸 쪽 기준: 서비스로 향한 요청 수요

이 변경의 의미는 단순합니다. Pod가 readiness 실패로 요청을 못 받는 순간에도, source 관점에서는 여전히 요청이 발생하고 있다는 사실을 볼 수 있습니다. 그러면 오토스케일링은 “처리된 양이 줄었으니 한가하다”가 아니라 “들어오는 요청이 많으니 Pod를 늘려야 한다”에 가깝게 판단합니다.

실제 요청량(수요)HPA: 수요↑ → scale-out ↑요청ServicePodPodPod+newPod+new

물론 이것만으로 모든 장애가 자동 복구되는 것은 아닙니다. readiness 실패의 원인, 애플리케이션 병목, 리소스 limit 같은 문제는 별도로 봐야 합니다. 하지만 적어도 이번 문제에서는 회복에 필요한 신호가 스케일러까지 도달하지 못한 것이 핵심이었고, reporter 관점을 바꾸는 것으로 그 신호를 되살릴 수 있었습니다.


정리

  • 예열이 부족해 첫 부하를 받아내지 못했고, 일부 Pod가 readiness 실패로 라우팅 대상에서 제외됐습니다.
  • 그 뒤에도 외부에서 들어오는 요청은 계속 많았지만, Pod가 실제로 처리한 요청은 줄었습니다.
  • 기존 오토스케일링은 받은 쪽 기준이라 처리된 요청 감소를 트래픽 감소로 해석했습니다.
  • 실제 Istio 쿼리에서는 reporter="destination"reporter="source"로 바꾸자 들어오는 수요가 지표에 남았고, 스케일링이 회복에 필요한 방향으로 동작할 수 있었습니다.

이번 문제는 “트래픽 기반 스케일링”이라는 말만으로는 충분하지 않다는 걸 보여줬습니다. 중요한 건 어떤 트래픽을 보느냐입니다. 장애 순간에는 처리량보다 수요를 봐야 할 때가 있습니다.