istiod에 resource limits를 빠뜨리면 생기는 일 (QoS class · 노드 안정성)
[istio-ingressgateway HPA min replica 조정] 글을 정리하면서 자연스럽게 따라온 점검 항목이 하나 있었습니다.
istiod 의 Deployment 정의를 들여다봤더니, requests 는 있는데 limits 가 없었습니다.
resources:
requests:
cpu: 500m
memory: 2Gi
# limits: 없음
운영 환경에서 limits 가 빠져 있는 컴포넌트는 평소엔 보이지 않다가, 부하가 몰릴 때 노드 전체의 안정성에 영향을 주기 시작합니다.
이번 글에서는 limits 가 빠진 상태가 의미하는 것 을 짚어보고, istiod 처럼 컨트롤 플레인 역할을 하는 컴포넌트에서 limits 를 어떻게 잡으면 좋은지를 정리합니다.
1. 발단
kubectl get deploy istiod -n istio-system -o yaml 로 들여다본 정의는 단순했습니다.
spec:
template:
spec:
containers:
- name: discovery
resources:
requests:
cpu: 500m
memory: 2Gi
limits 자체가 정의되어 있지 않은 상태입니다. Helm chart 의 기본값을 그대로 두고 운영한 결과인데, 이게 의미하는 바를 한 줄로 정리하면 이렇습니다.
컨테이너가 이론적으로 노드의 모든 CPU / 메모리 를 사용할 수 있는 상태.
평시에는 istiod 가 그렇게 많은 리소스를 쓰지 않습니다. 다만 다음 조건이 한꺼번에 겹치면 이야기가 달라집니다.
- 클러스터에 새 워크로드가 대량으로 떠서 sidecar config 가 일시적으로 폭증
- 메모리 누수 또는 비정상 동작
- 노드 자체에 다른 무거운 Pod 가 함께 떠 있는 경우
2. QoS class 의 의미
K8s 는 컨테이너의 resources 정의에 따라 QoS class 를 자동으로 부여합니다.
| QoS class | 조건 | 노드 압박 시 |
|---|---|---|
| Guaranteed | requests = limits, 둘 다 명시 | 가장 마지막에 OOMKill 후보 |
| Burstable | requests 만 있거나, requests < limits | Guaranteed 다음 후보 |
| BestEffort | requests 와 limits 모두 없음 | 가장 먼저 OOMKill 후보 |
이번 istiod 는 Burstable 입니다. limits 가 없어 노드 압박 시 가장 마지막은 아니지만, 그렇다고 노드가 충분히 보호되는 상태도 아닙니다.
“limits 가 없다” 는 의미는 컨테이너 자신이 보호받지 못한다 가 아니라, 노드의 다른 Pod 가 보호받지 못한다 에 가깝습니다.
istiod 가 메모리를 잡아먹기 시작하면, 같은 노드의 다른 Pod 가 먼저 영향을 받을 수 있는 구조입니다.
3. 컨트롤 플레인 컴포넌트가 노드 전체 자원을 점유하면
istiod 는 메시 안의 사이드카들에게 설정을 배포하는 컨트롤 플레인입니다. 이 한 컴포넌트가 흔들리면 다음 순서로 문제가 번집니다.
istiod 메모리 폭증
↓
같은 노드의 다른 Pod 가 메모리 부족으로 evict
↓
istiod 자신은 노드 전체 메모리를 점유한 채로 도는 중
↓
노드의 kubelet/kube-proxy 같은 시스템 컴포넌트도 영향
↓
노드 자체가 NotReady 로 전환될 가능성
운영 측면에서 가장 피하고 싶은 모양은 마지막 단계입니다. 한 Pod 의 문제가 노드 전체로 번지면, 그 노드 위의 다른 워크로드까지 함께 영향을 받습니다.
limits 한 줄이 막아주는 게 정확히 이 마지막 단계입니다.
4. limits 값을 어떻게 잡을 것인가
기본 가이드는 단순합니다.
- 평시 실제 사용량을 메트릭으로 측정
- 그 사용량의 2~3배 를 limits 로 잡기
- requests 는 평시 사용량 부근에 두기
istiod 의 경우 다음 메트릭을 보면 산정이 빨라집니다.
kubectl top pod -n istio-system -l app=istiod
MEMORY(bytes) 와 CPU(cores) 를 며칠 동안 관찰해서 피크값을 잡습니다.
예시: 평시 메모리가 1.2Gi 부근에서 안정적이고 피크가 1.8Gi 까지 올라가는 환경이라면, limits 는 4Gi 정도가 자연스럽습니다.
resources:
requests:
cpu: 500m
memory: 2Gi
limits:
cpu: "2"
memory: 4Gi
CPU limits 의 경우 throttle 동작이 의외로 까다로워, 다음 두 가지 사고 방식이 있습니다.
| 방식 | 의미 |
|---|---|
CPU limits 명시 (예: "2") | 한 컨테이너가 CPU 를 독점하지 못함. 다만 throttle 로 인한 latency 증가 가능성 |
| CPU limits 생략 (memory limits 만) | 컨테이너가 노드 CPU 를 일시적으로 더 쓸 수 있음. 다만 노드 압박 시 다른 Pod 가 영향 받을 수 있음 |
운영 측면에서는 memory limits 는 반드시 잡고, CPU limits 는 환경에 따라 결정 하는 패턴이 흔합니다.
memory 는 한 번 점유하면 회수가 어렵지만, CPU 는 시분할이라 순간 점유 후 자동으로 풀립니다.
5. 변경 절차
운영 중인 istiod 의 limits 를 추가하는 흐름입니다.
5-1. 백업
kubectl get deploy istiod -n istio-system -o yaml > /tmp/istiod-before.yaml
5-2. patch
kubectl patch deploy istiod -n istio-system --type=strategic -p '
spec:
template:
spec:
containers:
- name: discovery
resources:
requests:
cpu: 500m
memory: 2Gi
limits:
cpu: "2"
memory: 4Gi
'
5-3. rollout 진행
kubectl rollout status deploy istiod -n istio-system
istiod 는 replica 가 여러 개 떠 있는 경우 rolling update 로 진행되므로, 컨트롤 플레인 자체의 다운타임은 거의 없습니다.
replica 가 1이라면 짧은 다운타임이 발생할 수 있습니다. 이런 환경이라면 limits 변경 작업을 시작하기 전에 replicas 를 일시 증설하는 게 안전합니다.
5-4. QoS class 변경 확인
kubectl get pod -n istio-system -l app=istiod \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.qosClass}{"\n"}{end}'
Burstable 로 유지된 상태일 수도 있고, requests = limits 로 잡았다면 Guaranteed 가 됩니다.
Guaranteed: requests 와 limits 가 완전히 동일Burstable: requests < limits 또는 한쪽만 있음
istiod 처럼 평시-피크 차이가 큰 컴포넌트는 Burstable 이 자연스럽고, 완전히 고정된 부하의 컴포넌트라면 Guaranteed 가 더 안전합니다.
6. GitOps 환경에서의 적용 방식
ArgoCD 같은 GitOps 도구로 istio 가 관리되고 있다면, kubectl patch 는 self-heal 로 되돌아갑니다.
직접 patch 하지 말고, values / manifest 에 반영 → commit → push 흐름으로 가야 합니다.
Helm chart 라면 보통 다음 위치입니다.
# istio operator / istiod values
pilot:
resources:
requests:
cpu: 500m
memory: 2Gi
limits:
cpu: "2"
memory: 4Gi
GitOps 흐름은 별도 글([Karpenter 잔재 NodeClaim · Node finalizer 정리 절차] 의 ArgoCD self-heal 섹션 참고) 에서 다뤘던 패턴과 동일합니다.
7. 짚어두고 싶은 것들
7-1. limits 없는 컴포넌트는 노드 압박 시 주변을 끌고 갈 수 있음
limits 가 없는 컨테이너는 자기 자신이 위험한 게 아니라, 노드 전체의 안정성을 위협 합니다.
특히 컨트롤 플레인 / 시스템 컴포넌트가 이 상태라면 영향 범위가 매우 커집니다.
7-2. 평시 사용량의 2~3배가 기본 출발점
limits 값은 메트릭 기반으로 잡는 게 정석입니다.
- 평시 사용량 측정 (
kubectl top또는 Prometheus) - 피크값 확인 (며칠~몇 주)
- 2~3배 마진을 둔 값으로 시작
처음부터 정확한 값을 잡으려고 하기보다, 일단 보수적으로 잡아두고 운영 메트릭을 보면서 조정하는 패턴이 안전합니다.
7-3. memory limits 는 반드시, CPU limits 는 환경에 따라
memory 는 한 번 점유하면 회수가 어려운 자원이라 memory limits 는 거의 무조건 잡는 게 좋습니다.
CPU 는 시분할로 자동 회수되어, limits 를 잡으면 throttle 로 인한 latency 가 늘어날 수 있습니다. 환경에 따라 다음과 같이 선택합니다.
- 멀티 테넌트 / 노드 자원이 빠듯한 환경 → CPU limits 도 잡기
- 단일 서비스 전용 노드 → CPU limits 생략 가능
7-4. QoS class 는 운영 시점에 한 번 점검
운영 환경의 Pod 들이 어느 QoS class 에 들어있는지를 한 번 일괄 점검해두면, limits 가 빠진 컴포넌트를 한 화면에 잡아낼 수 있습니다.
kubectl get pod -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.status.qosClass}{"\n"}{end}' \
| grep -E "BestEffort|Burstable" | head
BestEffort 는 운영 환경에서는 거의 보지 말아야 할 상태입니다. Burstable 도 컨트롤 플레인 / 핵심 컴포넌트라면 한 번 들여다보는 게 좋습니다.
7-5. limits 변경 자체는 가벼운 작업
운영 입장에서 limits 추가는 rolling update 한 번으로 끝나는 가벼운 작업입니다. 어렵게 미루지 말고, 자원 정의가 빠진 컴포넌트를 발견했을 때 그 자리에서 정리 하는 게 좋습니다.
8. 마무리
requests 만 있는 상태가 곧 운영 위험이라고 단정할 수는 없습니다. 다만 언제 위험이 발현될지 통제할 수 없는 상태 라는 점이 운영자 입장에서 부담입니다.
limits 한 줄이 막아주는 건 자기 자신이 아니라 주변 Pod 와 노드 전체.
이번 점검을 계기로 istio-system 외의 다른 컨트롤 플레인 컴포넌트들도 같은 시각으로 한 번씩 들여다보고, limits 가 빠진 것들을 정리해 둘 예정입니다.
같은 흐름에서 다음으로 정리할 주제는, Istio 컨트롤 플레인 운영 점검 체크리스트 (HPA / resources / PDB / 사이드카 주입 정책 등을 한 화면으로 보는 정리 글) 입니다.