[K8s] Quota

홍종훈·2024년 3월 31일

K8s

목록 보기
2/2

Quota

여러 사용자나 팀이 정해진 수의 노드로 클러스터를 공유할 때 한 팀이 공정하게 분배된 리소스보다 많은 리소스를 사용할 수 있다는 우려가 있다. 리소스 쿼터는 관리자가 이 문제를 해결하기 위한 도구이다.

ResourceQuota 오브젝트로 정의된 리소스 쿼터는 네임스페이스별 총 리소스 사용을 제한하는 제약 조건을 제공한다. 유형별로 네임스페이스에서 만들 수 있는 오브젝트 수와 해당 네임스페이스의 리소스가 사용할 수 있는 총 컴퓨트 리소스의 양을 제한할 수 있다.

1. 리소스 쿼터의 작동

  • 다른 팀은 다른 네임스페이스에서 작업한다. 이것은 RBAC으로 설정할 수 있다.
  • 관리자는 각 네임스페이스에 대해 하나의 리소스쿼터를 생성한다.
  • 사용자는 네임스페이스에서 리소스(파드, 서비스 등)를 생성하고 쿼터 시스템은 사용량을 추적하여 리소스쿼터에 정의된 하드(hard) 리소스 제한을 초과하지 않도록 한다.
  • 리소스를 생성하거나 업데이트할 때 쿼터 제약 조건을 위반하면 위반된 제약 조건을 설명하는 메시지와 함께 HTTP 상태 코드 403 FORBIDDEN으로 요청이 실패한다.
  • cpumemory와 같은 컴퓨트 리소스에 대해 네임스페이스에서 쿼터가 활성화된 경우 사용자는 해당값에 대한 요청 또는 제한을 지정해야 한다. 그렇지 않으면 쿼터 시스템이 파드 생성을 거부할 수 있다.

네임스페이스와 쿼터를 사용하여 만들 수 있는 정책의 예는 다음과 같다.

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-quota
  namespace: team-a
spec:
  hard:
    requests.cpu: "10"
    requests.memory: 20Gi
    limits.cpu: "16"
    limits.memory: 32Gi
    pods: "100"
    services: "10"
    secrets: "20"

이 리소스 쿼터는 team-a 네임스페이스에서 CPU 요청을 최대 10 코어, 메모리 요청을 최대 20Gi, CPU 제한을 최대 16 코어, 메모리 제한을 최대 32Gi로 제한한다. 또한, 최대 100개의 파드, 10개의 서비스, 20개의 secerts을 생성할 수 있다.

apiVersion: v1
kind: ResourceQuota
metadata:
  name: testing-quota
  namespace: testing
spec:
  hard:
    requests.cpu: "1"
    requests.memory: 1Gi
    limits.cpu: "2"
    limits.memory: 2Gi
    pods: "10"
    services: "5"
    secrets: "10"

testing 네임스페이스용 리소스 쿼터는 CPU 요청을 최대 1 코어, 메모리 요청을 최대 1Gi, CPU 제한을 최대 2 코어, 메모리 제한을 최대 2Gi로 제한한다. 이는 testing 환경의 리소스 사용을 제한하기 위한 것으로, 최대 10개의 파드, 5개의 서비스, 10개의 secerts을 생성할 수 있다.

2. 컴퓨트 리소스 쿼터

지정된 네임스페이스에서 요청할 수 있는 총 컴퓨트 리소스 합을 제한할 수 있다.

다음과 같은 리소스 유형이 지원된다.

리소스 이름설명
limits.cpu터미널이 아닌 상태의 모든 파드에서 CPU 제한의 합은 이 값을 초과할 수 없음.
limits.memory터미널이 아닌 상태의 모든 파드에서 메모리 제한의 합은 이 값을 초과할 수 없음.
requests.cpu터미널이 아닌 상태의 모든 파드에서 CPU 요청의 합은 이 값을 초과할 수 없음.
requests.memory터미널이 아닌 상태의 모든 파드에서 메모리 요청의 합은 이 값을 초과할 수 없음.
hugepages-터미널 상태가 아닌 모든 파드에 걸쳐서, 지정된 사이즈의 휴즈 페이지 요청은 이 값을 초과하지 못함.
cpurequests.cpu 와 같음.
memoryrequests.memory 와 같음.
  • 터미널이 아닌 상태(Non-terminal states):
    • Pending: 파드가 쿠버네티스 클러스터에 의해 수락되었지만, 하나 이상의 컨테이너 이미지 생성이 진행 중이거나 아직 시작되지 않았을 때의 상태이다. 예를 들어, 이미지를 다운로드 중일 수 있다.
    • Running: 파드가 노드에 바인딩되었고, 모든 컨테이너가 생성되었으며, 적어도 하나의 컨테이너가 실행 중인 상태이다.
  • 터미널 상태(Terminal states):
    • Succeeded: 파드 내의 모든 컨테이너가 성공적으로 실행을 마치고 종료되었습니다. 이 상태의 파드는 재시작하지 않는다.
    • Failed: 파드 내의 모든 컨테이너가 종료되었고, 적어도 하나의 컨테이너가 실패 상태로 종료되었습니다. 실패 상태는 컨테이너가 0이 아닌 상태로 종료되었음을 의미다.

쿠버네티스에서 리소스 쿼터를 설정할 때 "터미널이 아닌 상태의 모든 파드에서 CPU 제한의 합"이라는 제한을 사용하면, 실행 중이거나 실행을 기다리고 있는 파드의 CPU 사용량에 대한 총량을 제한할 수 있다.

3. 스토리지 리소스 쿼터

지정된 네임스페이스에서 요청할 수 있는 총 스토리지 리소스 합을 제한할 수 있다.

또한 연관된 스토리지 클래스를 기반으로 스토리지 리소스 사용을 제한할 수 있다.

리소스 이름설명
requests.storage모든 퍼시스턴트 볼륨 클레임에서 스토리지 요청의 합은 이 값을 초과할 수 없음
persistentvolumeclaims네임스페이스에 존재할 수 있는 총 퍼시스턴스 볼륨 클레임 수
.storageclass.storage.k8s.io/requests.storagestorage-class-name과 관련된 모든 퍼시스턴트 볼륨 클레임에서 스토리지 요청의 합은 이 값을 초과할 수 없음
.storageclass.storage.k8s.io/persistentvolumeclaims 과 관련된 모든 퍼시스턴트 볼륨 클레임에서 네임스페이스에 존재할 수 있는 총 퍼시스턴스 볼륨 클레임 수

예를 들어, 운영자가 bronze 스토리지 클래스와 별도로 gold 스토리지 클래스를 사용하여 스토리지에 쿼터를 지정하려는 경우

운영자는 다음과 같이 쿼터를 정의할 수 있다.

  • gold.storageclass.storage.k8s.io/requests.storage: 500Gi
  • bronze.storageclass.storage.k8s.io/requests.storage: 100Gi

릴리스 1.8에서는 로컬 임시 스토리지에 대한 쿼터 지원이 알파 기능으로 추가되었다.

리소스 이름설명
requests.ephemeral-storage네임스페이스의 모든 파드에서 로컬 임시 스토리지 요청의 합은 이 값을 초과할 수 없음.
limits.ephemeral-storage네임스페이스의 모든 파드에서 로컬 임시 스토리지 제한의 합은 이 값을 초과할 수 없음.
ephemeral-storagerequests.ephemeral-storage 와 같음.
apiVersion: v1
kind: ResourceQuota
metadata:
  name: storage-and-ephemeral-quota
  namespace: example-namespace
spec:
  hard:
    # 전체 네임스페이스의 퍼시스턴트 볼륨 클레임(PVC) 요청 스토리지 합계 제한
    requests.storage: "500Gi"
    # 네임스페이스에서 허용되는 총 PVC 수
    persistentvolumeclaims: "10"
   
    # gold 스토리지 클래스에 대한 스토리지 요청 합계 제한
    gold.storageclass.storage.k8s.io/requests.storage: "300Gi"
    # gold 스토리지 클래스에 대한 PVC 수 제한
    gold.storageclass.storage.k8s.io/persistentvolumeclaims: "5"
    
    # bronze 스토리지 클래스에 대한 스토리지 요청 합계 제한
    bronze.storageclass.storage.k8s.io/requests.storage: "200Gi"
    # bronze 스토리지 클래스에 대한 PVC 수 제한
    bronze.storageclass.storage.k8s.io/persistentvolumeclaims: "5"
    
    # 네임스페이스의 모든 파드에서 로컬 임시 스토리지 요청 합계 제한
    requests.ephemeral-storage: "100Gi"
    # 네임스페이스의 모든 파드에서 로컬 임시 스토리지 제한 합계 제한
    limits.ephemeral-storage: "200Gi"

이 파일은 example-namespace 네임스페이스 내에서 스토리지 사용에 대한 다양한 제한을 설정한다.

여기에는 전체 스토리지 요청, 스토리지 클래스별 스토리지 요청, 전체 및 스토리지 클래스별 퍼시스턴트 볼륨 클레임(PVC) 수, 그리고 임시 스토리지에 대한 요청 및 제한이 포함된다.

4. 오브젝트 수 쿼터

다음 구문을 사용하여 모든 표준 네임스페이스 처리된(namespaced) 리소스 유형에 대한 특정 리소스 전체 수에 대하여 쿼터를 지정할 수 있다.

  • 코어 그룹이 아닌(non-core) 리소스를 위한 count/<resource>.<group>
  • 코어 그룹의 리소스를 위한 count/<resource>

다음은 사용자가 오브젝트 수 쿼터 아래에 배치하려는 리소스 셋의 예이다.

  • count/persistentvolumeclaims
  • count/services
  • count/secrets
  • count/configmaps
  • count/replicationcontrollers
  • count/deployments.apps
  • count/replicasets.apps
  • count/statefulsets.apps
  • count/jobs.batch
  • count/cronjobs.batch

사용자 정의 리소스를 위해 동일한 구문을 사용할 수 있다.

예를 들어 example.com API 그룹에서 widgets 사용자 정의 리소스에 대한 쿼터를 생성하려면 count/widgets.example.com을 사용한다.

count/* 리소스 쿼터를 사용할 때 서버 스토리지 영역에 있다면 오브젝트는 쿼터에 대해 과금된다.

이러한 유형의 쿼터는 스토리지 리소스 고갈을 방지하는 데 유용하다.

예를 들어, 크기가 큰 서버에서 시크릿 수에 쿼터를 지정할 수 있다. 클러스터에 시크릿이 너무 많으면 실제로 서버와 컨트롤러가 시작되지 않을 수 있다.

잘못 구성된 크론 잡으로부터의 보호를 위해 잡의 쿼터를 설정할 수 있다.

네임스페이스 내에서 너무 많은 잡을 생성하는 크론 잡은 서비스 거부를 유발할 수 있다.

또한 제한된 리소스 셋에 대해서 일반 오브젝트 수(generic object count) 쿼터를 적용하는 것도 가능하다. 다음 유형이 지원된다.

리소스 이름설명
configmaps네임스페이스에 존재할 수 있는 총 컨피그맵 수
persistentvolumeclaims네임스페이스에 존재할 수 있는 총 퍼시스턴스 볼륨 클레임 수
pods네임스페이스에 존재할 수 있는 터미널이 아닌 상태의 파드의 총 수. .status.phase in (Failed, Succeeded)가 true인 경우 파드는 터미널 상태임
replicationcontrollers네임스페이스에 존재할 수 있는 총 레플리케이션컨트롤러 수
resourcequotas네임스페이스에 존재할 수 있는 총 리소스쿼터 수
services네임스페이스에 존재할 수 있는 총 서비스 수
services.loadbalancers네임스페이스에 존재할 수 있는 LoadBalancer 유형의 총 서비스 수
services.nodeports네임스페이스에 존재할 수 있는 NodePort 유형의 총 서비스 수
secrets네임스페이스에 존재할 수 있는 총 시크릿 수

예를 들어, pods 쿼터는 터미널이 아닌 단일 네임스페이스에서 생성된 pods 수를 계산하고 최댓값을 적용한다.

사용자가 작은 파드를 많이 생성하여 클러스터의 파드 IP 공급이 고갈되는 경우를 피하기 위해 네임스페이스에 pods 쿼터를 설정할 수 있다.

apiVersion: v1
kind: ResourceQuota
metadata:
  name: object-counts-quota
  namespace: example-namespace
spec:
  hard:
    # 코어 그룹 리소스
    count/persistentvolumeclaims: "10"
    count/services: "10"
    count/secrets: "20"
    count/configmaps: "10"
    count/replicationcontrollers: "5"
    count/pods: "50"
   
    # 서비스 유형별 쿼터
    count/services.loadbalancers: "2"
    count/services.nodeports: "4"
    
    # 코어가 아닌 그룹 리소스
    count/deployments.apps: "10"
    count/replicasets.apps: "20"
    count/statefulsets.apps: "10"
    count/jobs.batch: "15"
    count/cronjobs.batch: "10"
    
    # 사용자 정의 리소스 (예시)
    count/widgets.example.com: "25"

위의 예시에서는 다음과 같은 리소스 쿼터를 설정한다:

  • 퍼시스턴트 볼륨 클레임(PVCs), 서비스, 시크릿, 컨피그맵, 레플리케이션컨트롤러, 파드 등의 코어 리소스에 대한 오브젝트 수 제한.
  • 로드밸런서와 노드포트 유형의 서비스에 대한 제한.
  • 애플리케이션, 레플리카셋, 스테이트풀셋, 잡, 크론잡 등의 코어가 아닌 리소스 그룹에 대한 제한.
  • widgets라는 사용자 정의 리소스에 대한 오브젝트 수 제한.

네임스페이스 내에서 리소스 쿼터를 설정함으로써, 관리자는 리소스 고갈로부터 시스템을 보호할 수 있다.

5. PriorityClass별 리소스 쿼터

기능 상태: Kubernetes v1.17 [stable]

특정 우선 순위로 파드를 생성할 수 있다. 쿼터 스펙의 scopeSelector 필드를 사용하여 파드의 우선 순위에 따라 파드의 시스템 리소스 사용을 제어할 수 있다.

쿼터 스펙의 scopeSelector가 파드를 선택한 경우에만 쿼터가 일치하고 사용된다.

scopeSelector 필드를 사용하여 우선 순위 클래스의 쿼터 범위를 지정하면, 쿼터 오브젝트는 다음의 리소스만 추적하도록 제한된다.

  • pods
  • cpu
  • memory
  • ephemeral-storage
  • limits.cpu
  • limits.memory
  • limits.ephemeral-storage
  • requests.cpu
  • requests.memory
  • requests.ephemeral-storage

이 예에서는 쿼터 오브젝트를 생성하여 특정 우선 순위의 파드와 일치시킨다. 예제는 다음과 같이 작동한다.

  • 클러스터의 파드는 "low(낮음)", "medium(중간)", "high(높음)"의 세 가지 우선 순위 클래스 중 하나를 가진다.
  • 각 우선 순위마다 하나의 쿼터 오브젝트가 생성된다.
  1. 다음 YAML을 quota.yml 파일에 저장한다.
apiVersion: v1
kind: List
items:
- apiVersion: v1
  kind: ResourceQuota
  metadata:
    name: pods-high
  spec:
    hard:
      cpu: "1000"
      memory: 200Gi
      pods: "10"
    scopeSelector:
      matchExpressions:
      - operator : In
        scopeName: PriorityClass
        values: ["high"]
- apiVersion: v1
  kind: ResourceQuota
  metadata:
    name: pods-medium
  spec:
    hard:
      cpu: "10"
      memory: 20Gi
      pods: "10"
    scopeSelector:
      matchExpressions:
      - operator : In
        scopeName: PriorityClass
        values: ["medium"]
- apiVersion: v1
  kind: ResourceQuota
  metadata:
    name: pods-low
  spec:
    hard:
      cpu: "5"
      memory: 10Gi
      pods: "10"
    scopeSelector:
      matchExpressions:
      - operator : In
        scopeName: PriorityClass
        values: ["low"]
  1. kubectl create를 사용하여 YAML을 적용한다.

    kubectl create -f ./quota.yml

resourcequota/pods-high created
resourcequota/pods-medium created
resourcequota/pods-low created
  1. kubectl describe quota를 사용하여 Used 쿼터가 0인지 확인하자.

    kubectl describe quota

Name:       pods-high
Namespace:  default
Resource    Used  Hard
--------    ----  ----
cpu         0     1k
memory      0     200Gi
pods        0     10

Name:       pods-low
Namespace:  default
Resource    Used  Hard
--------    ----  ----
cpu         0     5
memory      0     10Gi
pods        0     10

Name:       pods-medium
Namespace:  default
Resource    Used  Hard
--------    ----  ----
cpu         0     10
memory      0     20Gi
pods        0     10
  1. 우선 순위가 "high"인 파드를 생성한다. 다음 YAML을 high-priority-pod.yml 파일에 저장한다.
apiVersion: v1
kind: Pod
metadata:
  name: high-priority
spec:
  containers:
  - name: high-priority
    image: ubuntu
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo hello; sleep 10;done"]
    resources:
      requests:
        memory: "10Gi"
        cpu: "500m"
      limits:
        memory: "10Gi"
        cpu: "500m"
  priorityClassName: high
  1. kubectl create로 적용하자.

    kubectl create -f ./high-priority-pod.yml

  2. "high" 우선 순위 쿼터가 적용된 pods-high에 대한 "Used" 통계가 변경되었고 다른 두 쿼터는 변경되지 않았는지 확인한다.

    kubectl describe quota

profile
Search Engineer

0개의 댓글