[CloudNeta] EKS Best Practices - Scalability: Kubernetes Scaling Theory

원문: Scalability - Kubernetes Scaling Theory

이 문서는 AWS EKS Best Practices Guide의 Scalability 섹션 중 Kubernetes Scaling Theory 페이지를 분석한 글입니다. Kubernetes 클러스터의 확장성을 이해하기 위한 이론적 토대를 제공합니다.

Nodes vs. Churn Rate

Kubernetes의 확장성을 논의할 때, 흔히 단일 클러스터의 노드 수에 초점을 맞춥니다. 하지만 이는 확장성을 이해하는 데 가장 유용한 지표가 아닐 때가 많습니다.

예시:

핵심 관점 전환

노드 수가 아닌 **변화율(Rate of Change)**로 확장성을 바라보아야 합니다. 특정 시간(보통 Prometheus 기본값인 5분 간격) 내에 발생하는 변화율이 무엇을 튜닝해야 원하는 스케일을 달성할 수 있는지 더 잘 알려줍니다.

Thinking in Queries Per Second

Kubernetes의 각 컴포넌트에는 다음 단계의 컴포넌트를 과부하로부터 보호하는 메커니즘이 있습니다:

이러한 보호 메커니즘은 일반적으로 QPS(Queries Per Second) 단위로 표현됩니다.

QPS 설정 변경 시 주의

QPS 설정을 변경할 때는 매우 신중해야 합니다. 하나의 병목을 제거하면(예: Kubelet의 QPS 증가) 하류 컴포넌트에 영향을 미칩니다. 특정 속도 이상에서 시스템을 과부하시킬 수 있으므로, 서비스 체인의 각 부분을 이해하고 모니터링하는 것이 핵심입니다.

API Priority and Fairness

API 서버는 API Priority and Fairness라는 더 복잡한 시스템을 도입하여 별도로 구성할 수 있습니다.

메트릭 오해 주의

일부 메트릭은 올바른 용도처럼 보이지만 실제로는 다른 것을 측정합니다. 예를 들어 kubelet_http_inflight_requests는 Kubelet의 메트릭 서버에 대한 요청만 측정하며, Kubelet에서 API 서버로의 요청 수를 측정하는 것이 아닙니다. 이로 인해 Kubelet의 QPS 플래그를 잘못 구성할 수 있습니다. 특정 Kubelet의 감사 로그(audit logs)를 쿼리하는 것이 더 신뢰할 수 있는 방법입니다.

Scaling Distributed Components

EKS는 관리형 서비스이므로, Kubernetes 컴포넌트를 두 가지 범주로 나눌 수 있습니다:

graph LR
    subgraph AWS_MANAGED["AWS 관리 영역"]
        ETCD[etcd]
        KCM[Kube Controller Manager]
        SCHED[Scheduler]
    end

    subgraph API["API Server"]
        APIS[API Server
API Priority & Fairness
고객 설정 가능] end subgraph CUSTOMER["고객 설정 가능 영역"] KUBELET[Kubelet] RUNTIME[Container Runtime] CNI[네트워크 드라이버
CNI] CSI[스토리지 드라이버
CSI] end ETCD --- APIS KCM --- APIS SCHED --- APIS APIS --- KUBELET KUBELET --- RUNTIME KUBELET --- CNI KUBELET --- CSI style AWS_MANAGED fill:#232f3e,color:#fff,stroke:#ff9900,stroke-width:2px style API fill:#1a472a,color:#fff,stroke:#ff9900,stroke-width:2px style CUSTOMER fill:#0d3b66,color:#fff,stroke:#3b82f6,stroke-width:2px
구분 컴포넌트 관리 주체
AWS 관리 etcd, Kube Controller Manager, Scheduler AWS
중간 API Server (API Priority and Fairness 설정은 고객이 가능) AWS (설정은 고객)
고객 설정 가능 Kubelet, Container Runtime, CNI/CSI 등 AWS API 호출 오퍼레이터 고객

Upstream and Downstream Bottlenecks

서비스를 모니터링할 때, 양방향(upstream/downstream) 메트릭을 살펴보며 병목을 찾아야 합니다.

Kubelet을 예로 들면:

How many Pods per Node

Upstream에서 지원하는 노드당 Pod 수는 110개입니다. 하지만 실제 프로덕션 워크로드는 upstream 스케일링 테스트에서 검증된 것보다 복잡합니다. Kubelet이 Containerd 런타임을 "따라갈 수 있는지(keeping up)" 확인해야 합니다.

sequenceDiagram
    participant K as Kubelet
    participant C as Containerd

    loop Pod 상태 모니터링
        K->>C: Pod 상태 요청
        C-->>K: Pod 상태 응답
    end

    Note over K,C: Pod 상태 변경이 너무 빠르면
요청이 타임아웃됩니다

단순화하면, Kubelet은 컨테이너 런타임(Containerd)으로부터 Pod의 상태를 가져옵니다. 너무 많은 Pod가 너무 빠르게 상태 변경을 하면, 컨테이너 런타임에 대한 요청이 타임아웃될 수 있습니다.

PLEG 서브시스템 변경 진행중

Kubernetes는 끊임없이 진화하고 있으며, 이 서브시스템은 현재 변경이 진행 중입니다.
참고: KEP-3386

PLEG Duration 모니터링

Pod Lifecycle Event Generation(PLEG) duration 메트릭에서 **타임아웃 값에 평평한 선(flat line)**이 보이면, 해당 노드가 한계를 넘었다는 의미입니다.

아래 PromQL 쿼리로 확인할 수 있습니다:

increase(kubelet_pleg_relist_duration_seconds_bucket{instance="$instance"}[$__rate_interval])

타임아웃 발생 시 대응

타임아웃이 관찰되면, 원인을 해결한 후 다음 단계로 진행해야 합니다:

핵심

고정된 숫자(예: 110개)에 의존하기보다, 메트릭을 사용하여 해당 노드가 할당된 Pod의 Churn Rate를 처리할 수 있는지 판단하는 것이 최선입니다.

Scale by Metrics

메트릭으로 시스템을 최적화하는 것은 오래된 개념이지만, Kubernetes 여정 초기에 종종 간과됩니다.

원칙

특정 숫자(예: 노드당 110개 Pod)에 집중하는 대신, 시스템의 병목을 찾아주는 메트릭을 찾는 데 노력을 집중해야 합니다. 이러한 메트릭의 올바른 임계값을 이해하면, 시스템이 최적으로 구성되었다는 높은 확신을 가질 수 있습니다.

The Impact of Changes

문제를 해결할 때 흔히 저지르는 실수는 의심스러워 보이는 첫 번째 메트릭이나 로그 에러에 집중하는 것입니다.

앞서 Kubelet 타임아웃을 봤을 때, Kubelet의 초당 전송 속도를 증가시키는 등 무작위 조치를 시도할 수 있습니다. 하지만 에러가 발생한 지점의 하류(downstream) 전체를 먼저 살펴보는 것이 현명합니다.

원칙

각 변경은 목적이 있어야 하고, 데이터에 의해 뒷받침되어야 합니다.

Kubelet의 하류 컴포넌트:

graph TD
    KUBELET[Kubelet] --> CONTAINERD[Containerd Runtime
Pod 에러] KUBELET --> DS_CSI[DaemonSet: Storage Driver
CSI] KUBELET --> DS_CNI[DaemonSet: Network Driver
CNI] DS_CSI --> EC2_API[EC2 API] DS_CNI --> EC2_API style KUBELET fill:#232f3e,color:#fff,stroke:#ff9900,stroke-width:2px style CONTAINERD fill:#0d3b66,color:#fff,stroke:#3b82f6,stroke-width:2px style DS_CSI fill:#1a472a,color:#fff,stroke:#059669,stroke-width:2px style DS_CNI fill:#1a472a,color:#fff,stroke:#059669,stroke-width:2px style EC2_API fill:#7c3aed,color:#fff,stroke:#8b5cf6,stroke-width:2px

노드를 너무 밀집하게 bin packing하면 여러 지점에서 에러가 발생할 수 있습니다.

간과하기 쉬운 신호

워크로드에 적합한 노드 크기를 설계할 때, 이러한 신호들은 간과하기 쉽지만 시스템에 불필요한 압력을 가하여 스케일과 성능을 모두 제한할 수 있습니다.

The Cost of Unnecessary Errors

Kubernetes 컨트롤러는 에러 상황에서 재시도에 탁월하지만, 이에는 비용이 따릅니다:

에러가 적을수록 시스템의 문제를 더 쉽게 발견할 수 있습니다. 주요 운영(업그레이드 등) 전에 클러스터가 에러 프리 상태인지 주기적으로 확인하면, 예기치 않은 이벤트 발생 시 로그 트러블슈팅이 간소화됩니다.

Expanding Our View

1,000개 이상의 노드를 가진 대규모 클러스터에서는 개별 병목을 하나씩 찾는 것이 비현실적입니다. PromQL의 topk 함수를 사용하면 데이터셋에서 가장 높은 값을 찾을 수 있습니다.

클러스터의 모든 Kubelet이 포화 상태인지 확인하기 위해, Kubelet이 **이벤트를 폐기(discard)**하고 있는지 살펴봅시다:

topk(3, increase(kubelet_pleg_discard_events{}[$__rate_interval]))

쿼리 분석:

구성요소 설명
$__rate_interval Grafana 변수. 4개의 샘플을 확보하기 위한 변수로, 모니터링의 복잡한 주제를 간단한 변수로 우회합니다.
topk 상위 결과만 반환합니다.
3 결과를 3개 노드로 제한합니다. 클러스터 전체 메트릭에 유용한 함수입니다.
{} 필터 없음. 일반적으로 스크래핑 규칙의 job 이름을 넣지만, 이름이 다양하므로 비워둡니다.

Splitting the Problem in Half

시스템의 병목을 해결하기 위해, upstream 또는 downstream에 문제가 있음을 보여주는 메트릭을 찾아 문제를 반으로 나누는 접근법을 사용합니다. 이는 메트릭 데이터를 표시하는 방식의 핵심 원칙이기도 합니다.

이 프로세스의 좋은 시작점은 API Server입니다. API Server를 통해 문제가 다음 중 어디에 있는지 파악할 수 있습니다:

graph LR
    CLIENT[클라이언트 애플리케이션
Kubelet, Operators 등] -->|upstream| API[API Server
관측 지점] API -->|downstream| CP[컨트롤 플레인
etcd, KCM, Scheduler] style API fill:#ff9900,color:#000,stroke:#232f3e,stroke-width:3px style CLIENT fill:#0d3b66,color:#fff,stroke:#3b82f6,stroke-width:2px style CP fill:#232f3e,color:#fff,stroke:#ff9900,stroke-width:2px

정리

원칙 설명
노드 수보다 변화율 클러스터의 확장성은 노드 수가 아닌 단위 시간당 변화율(Churn Rate)로 이해해야 합니다.
QPS 기반 사고 각 컴포넌트의 QPS 보호 메커니즘을 이해하고, 하나의 병목 제거가 하류에 미치는 영향을 고려해야 합니다.
메트릭 기반 판단 고정 숫자(110 pods/node 등)가 아닌 PLEG duration 등의 메트릭으로 실제 한계를 파악해야 합니다.
데이터 기반 변경 각 변경은 목적이 있어야 하며, 데이터에 의해 뒷받침되어야 합니다.
문제 이분법 API Server를 관측 지점으로 삼아 문제를 upstream/downstream으로 나누어 추적합니다.
에러 비용 인식 불필요한 에러와 재시도는 시스템 전체에 압력을 가하므로, 에러 프리 상태를 유지하는 것이 중요합니다.