LabHub
배우기 러닝패스 코스

CNPE — Cloud Native Platform Engineer

Read quotas, defaults and QoS as different measurements

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

ResourceQuota는 테넌트의 사용 가능한 상한이고, requests는 스케줄링의 입력이며, 실제 사용량은 관측값입니다. 상한·요청·실사용을 같은 숫자로 취급하지 않습니다.

Flow map: 상한·요청·실사용을 같은 숫자로 취급하지 않습니다. · 기존 파드가 자동으로 고쳐지지 않습니다. · 본인 전용 학습 클러스터 · 9코어를 요청한

왜 이게 필요했나

연습용 클러스터의 allocatable CPU가 24코어이고 세 테넌트의 requests.cpu 쿼터가 각각 12·9·9코어라고 합시다. 상한의 합은 30코어, 용량 대비 비율은 1.25입니다. 이것은 지금 30코어가 예약됐다는 뜻도, CPU를 125% 쓰고 있다는 뜻도 아닙니다. 모든 팀이 동시에 상한만큼 요청할 경우를 수용하지 못할 수 있다는 계획상의 신호입니다. 실제 배치된 파드의 requests 합과 사용량을 따로 조사해야 합니다.

팀별 상한의 합을 더하는 계산은 출발점일 뿐입니다. 특정 노드 풀이나 스토리지 영역에 몰린 워크로드는 전체 여유가 있어도 못 뜰 수 있고, 한 노드 장애 때의 여유도 필요합니다. 쿼터의 hard만 더해 서버 구매량을 결정하지 않습니다.

어떻게 동작하나

입장 심사와 자리 배정

ResourceQuota는 네임스페이스 범위의 자원·객체 수 제한을 admission에서 검사합니다. CPU 실사용을 계속 측정하는 장치는 아닙니다. count/appclaims.platform.labhub.io 같은 키로 CR 개수를 제한할 수도 있습니다. RBAC·정책·별도 플랫폼 로직도 발급을 제한할 수 있으므로 쿼터가 유일한 수단은 아닙니다. 공식 ResourceQuota 문서에서 계산 대상을 확인하세요.

LimitRange는 객체별 최솟값·최댓값·기본값 등을 다룹니다. default는 limit, defaultRequest는 request의 기본값입니다. 파드 생성 때 적용되며 정책을 바꿔도 기존 파드가 자동으로 고쳐지지 않습니다. 여러 LimitRange로 상충하는 기본값을 주지 마세요. 공식 LimitRange 문서를 바탕으로, 제출 전 파일과 admission 뒤 객체를 비교하는 습관을 들입니다.

Guaranteed에는 조건이 있습니다

이 설명은 컨테이너별 resources를 쓰고 파드 수준 resources는 선언하지 않는 예제입니다. Guaranteed가 되려면 모든 일반·init 컨테이너에서 CPU와 메모리 각각의 request와 limit이 존재하고 같아야 합니다. 메모리만 같은 값으로 채워져도 CPU 조건이 빠지면 Guaranteed가 아닙니다. 하나라도 자원 선언이 있고 Guaranteed 조건에 못 미치면 Burstable입니다. 공식 QoS 실습과 최종 status.qosClass를 대조합니다.

예제 컨테이너의 최종 자원 QoS 판단 이유
CPU 100m/100m, 메모리 64Mi/64Mi Guaranteed 두 자원의 request/limit이 모두 같다
CPU 없음, 메모리 64Mi/64Mi Burstable CPU 조건이 빠졌다
CPU 25m/100m, 메모리 32Mi/64Mi Burstable request가 limit보다 작다
모든 컨테이너에 CPU·메모리 선언 없음 BestEffort 두 자원 모두 request/limit이 없다

컨테이너가 limit을 명시하고 해당 request를 생략한 경우에는 request가 limit 값으로 설정됩니다. 이 경우와 ‘둘 다 생략해 LimitRange 기본값을 받는 경우’를 구분합니다. 이미 적힌 request는 기본값으로 덮는 대상이 아닙니다. 공식 메모리 기본값 예제를 읽을 때 이 두 입력을 바꾸어 예측해 보세요.

읽기 연습: 입력이 아니라 저장된 객체를 보세요

아래는 과금·운영 클러스터가 아닌 본인 전용 학습 클러스터에서만 실행할 예제입니다. cnpe-default-demo 네임스페이스가 이미 있으면 재사용하지 말고 새 이름을 고르세요. 먼저 기본값 두 개를 받는 파드를 예측한 다음 실제 결과를 읽습니다.

kubectl create namespace cnpe-default-demo
kubectl -n cnpe-default-demo apply -f - <<'YAML'
apiVersion: v1
kind: LimitRange
metadata:
  name: defaults
spec:
  limits:
  - type: Container
    default:
      cpu: 100m
      memory: 64Mi
YAML
kubectl -n cnpe-default-demo run sample --image=busybox:1.36 -- sleep 3600
kubectl -n cnpe-default-demo get pod sample -o jsonpath='{.spec.containers[0].resources}'
kubectl -n cnpe-default-demo get pod sample -o jsonpath='{.status.qosClass}'

예상은 CPU 100m·메모리 64Mi의 request와 limit, Guaranteed입니다. 이미지 다운로드 실패와 자원 기본값 적용 실패는 별개입니다. 컨테이너 실행까지 볼 때는 이미지 접근이 가능한 환경과 실제 kubelet이 필요합니다. kwok의 Running 표식은 실제 실행 증거로 쓰지 않습니다.

반례는 별도 네임스페이스에서 CPU 기본값만 빼고 새 파드를 만드는 것입니다. 메모리 request와 limit은 같아도 Burstable인지 확인합니다. 원래 네임스페이스의 정책만 바꾼 뒤 기존 파드를 읽는 것은 이 반례가 아닙니다. 기록을 남긴 후 본인이 만든 파드·정책만 든 네임스페이스를 삭제합니다.

kubectl delete namespace cnpe-default-demo

현장에서 만나는 모습

가상의 비용 회의에서 요청 4코어·실사용 0.3코어인 워크로드만 보고 바로 request를 0.3으로 내렸다고 합시다. 피크 시간·시작 비용·장애 여유를 보지 않았다면 절감액보다 장애가 먼저 생길 수 있습니다. 기간별 사용 분포와 SLO를 보고 작은 범위에서 변경한 뒤 스케줄링·지연·재시작을 함께 확인합니다. QoS 등급 자체가 성능이나 무장애를 보장하지는 않습니다.

쿼터의 사용량이 비어 있거나 status unknown 오류가 나면 객체의 status, CRD Established, API discovery와 컨트롤러 상태를 확인합니다. 무조건 삭제·재생성하면 admission 보호를 잠깐 없앨 수 있습니다. 원인과 복구 범위를 확인한 뒤 조치하고, 새 요청이 허용·거절되는 것을 각각 시험합니다.

관측 사례: 입장은 했는데 자리가 없습니다

2026-09-11, 별도의 단일 노드 k3s v1.36.4+k3s1 VM에서 다음을 실제로 확인했습니다. 운영 서비스의 부하 시험이 아니라, 작은 sleep 컨테이너로 admission과 스케줄링을 분리한 실험입니다. CPU를 9코어 쓰도록 부하를 건 것이 아니라 9코어를 요청한 것입니다.

확인한 필드 관측값
Node status.allocatable.cpu 8 이 노드의 파드용 CPU 용량
ResourceQuota status.hard.requests.cpu 10 이 Namespace의 요청량 상한
Pod spec.containers[0].resources.requests.cpu 9 단일 컨테이너가 요청한 CPU
ResourceQuota status.used.requests.cpu 9 아직 실행되지 않은 Pod의 요청도 계산됨
Pod status.phase / spec.nodeName Pending / 없음 API에 저장됐으나 노드 배치는 안 됨
PodScheduled 조건 False, Unschedulable, Insufficient cpu 스케줄러가 CPU 부족을 보고함

쿼터 10 안에 요청 9가 들어가므로 객체는 만들어졌지만, 노드 8에 요청 9를 놓을 수는 없었습니다. 이때 쿼터를 20으로 올려도 노드는 커지지 않습니다. 모든 노드가 8코어라면 같은 크기의 노드를 추가하는 것만으로도 이 단일 Pod는 들어가지 않습니다. 요청이 잘못 산정됐는지 검증하거나 충분히 큰 노드·애플리케이션 분할 등 설계 변경을 검토해야 합니다. 공식 자원 문제 해결 문서에서 노드보다 큰 Pod 사례를 대조하세요.

실험에서는 같은 이름의 Pod를 삭제한 뒤 요청 25m로 다시 만들었습니다. 새 UID가 발급됐고 Running·Ready와 실제 exec가 확인됐으며 쿼터 상한은 그대로였습니다. 실행 가능성 복구의 증거이지 적정 요청량이나 비용 절감의 증거는 아닙니다. sleep 컨테이너의 25m를 실제 주문 서버에 그대로 적용하지 마세요. CPU 사용 시계열, 처리량, 지연과 오류율은 이 실험에서 측정하지 않았습니다.

반대로 다른 Namespace에서 요청 50m인 Pod 두 개로 쿼터 100m를 채운 뒤 세 번째 50m Pod를 만들면, API가 Forbidden과 exceeded quota를 반환하고 세 번째 객체는 저장되지 않았습니다. 이 경우에는 스케줄러가 볼 Pod 자체가 없습니다. Deployment가 Pod 생성에 실패한 경우는 상위 객체의 이벤트도 확인해야 합니다. Pod의 Pending만 찾으면 admission 실패를 놓칩니다.

테넌트 경계는 읽기와 변경을 따로 시험합니다

같은 VM의 다른 실험에서는 ServiceAccount에 자기 Namespace의 pods get/list만 부여했습니다. 그 계정으로 실제 조회를 보내면 자기 Pod는 읽을 수 있었지만, 다른 Namespace의 Pod 조회와 자기 ResourceQuota 변경은 Forbidden이었습니다. 거절 뒤 관리자가 다시 읽은 쿼터도 100m 그대로였습니다. 단순히 Role YAML이 있다는 것보다 허용 요청·거절 요청·변경되지 않은 상태를 함께 보는 이유입니다.

이 실험은 관리자가 --as로 대리한 API 권한 검사입니다. 실제 로그인·토큰 발급·네트워크 차단·커널 격리까지 검증한 것은 아닙니다. 쿼터만 설정하고 테넌트에게 쿼터 수정 권한을 주면 상한을 스스로 올릴 수 있습니다. 반대로 API 조회를 막았다고 다른 팀의 서비스 포트로 통신할 수 없다는 뜻도 아닙니다. Kubernetes 멀티테넌시 문서의 제어 영역과 데이터 영역을 구분해 읽으세요.

스스로 판단해 보세요. ① 쿼터 used가 9인데 CPU가 9코어 실행 중인가요? ② 같은 이름으로 복구한 Pod가 기존 객체인가요? ③ 다른 팀 조회가 거절되면 네트워크 격리도 끝났나요? 답은 모두 아니며, 근거는 각각 요청량 계산·UID 변경·시험 범위의 차이입니다.

다음 퀴즈에서 확인할 것

24와 30이 무엇의 합인지, 메모리만 제한한 파드의 QoS, 기존 파드에 대한 정책 변경 효과를 판단하세요. 단일 정상 예제의 성공을 전체 테넌트 격리나 비용 최적화의 증거로 확대하지 않는 것이 이 단원의 목표입니다.