🔰

Kubernetesのリソースクォータとリソース制限

2022/10/13に公開

Kubernetesのリソースクォータとリソース制限

本記事では、Kubernetesにおいてコンテナが使用するCPUやメモリ等の計算資源を制限する方法を説明します。

なお、「計算資源」の英語訳は「リソース」(Resource)ですが、Kubernetesが管理するオブジェクトもまたリソースと呼ばれるため、以下では「リソース」という言葉は、後者のKubernetesが管理するオブジェクトを指すものとします。

Kubernetesリソースの分類

Kubernetesリソースは、概ね5つのカテゴリに分類されており、本記事で取り上げるリソースの対応関係は、次の通りです。

番号 カテゴリ 説明 リソース
1 Cluster APIs セキュリティや計算資源制限に関連するリソース群 Node, Namespace, Resource Quota
2 Config & Storage APIs 設定や永続化のためのストレージに関連するリソース群
3 Metadata APIs リソース制御に関連するリソース群 LimitRange
4 Service APIs コンテナの外部公開に関連するリソース群
5 Workloads APIs コンテナの実行に関連するリソース群

Nodeリソース

Nodeは普段、直接操作することはないリソースですが、ノード(サーバ)が持つ計算資源や現在使用中の計算資源を確認できる点では重要です。

まず、Node一覧を表示してみます。

kubectl get nodes

次に、特定のNodeの概要情報をYAMLに出力します。

kubectl get node <Node名> -o yaml

Nodeが持つ物理的な計算資源は、status.capacityで確認できます。
このcpumemoryの値は、ノードに実際に搭載されたCPUコア数やメモリ容量です。

[前略]
status:
  [中略]
  capacity:
    cpu: "6"
    ephemeral-storage: 61255492Ki
    hugepages-1Gi: "0"
    hugepages-2Mi: "0"
    memory: 2032964Ki
    pods: "110"
  [後略]

status.allocatableでは、Kubernetes自身が必要とする計算資源を差し引いたものが表示されています。

[前略]
status:
 [中略]
  allocatable:
    cpu: "6"
    ephemeral-storage: "56453061334"
    hugepages-1Gi: "0"
    hugepages-2Mi: "0"
    memory: 1930564Ki
    pods: "110"
  [後略]

現在使用中の計算資源を調べるため、特定のNodeの詳細情報を表示します。

kubectl describe node <Node名>

Nodeの詳細情報例です。

[前略]
  Namespace                   Name                                         CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
  ---------                   ----                                         ------------  ----------  ---------------  -------------  ---
  kube-system                 coredns-78fcd69978-f78q8                     100m (1%)     0 (0%)      70Mi (3%)        170Mi (9%)     231d
  kube-system                 coredns-78fcd69978-q9zgw                     100m (1%)     0 (0%)      70Mi (3%)        170Mi (9%)     231d
[中略]
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests     Limits
  --------           --------     ------
  cpu                850m (14%)   0 (0%)
  memory             240Mi (12%)  340Mi (18%)
  ephemeral-storage  0 (0%)       0 (0%)
  hugepages-1Gi      0 (0%)       0 (0%)
  hugepages-2Mi      0 (0%)       0 (0%)
[後略]

現在割り当てが可能な、余剰の計算資源は、上述のstatus.allocatableから、この詳細情報のAllocated resourcesを差し引いたものになります。

Eviction Manager

Eviction Managerは、Nodeリソースのkube-reserved, system-reserved, Allcatableの合計値がEviction Thresholdを超えていないかを常時確認し、超えている場合にはPodを強制的に除外(Evict)する処理を行なっています。
これにより、Kubernetesクラスターが高負荷になり続けるのを防いでいます。

Eviction Thresholdには、softとhardの2つのリミットが定義されています。

  • soft: リミットを超えたあと、terminationGracePeriodSecondsでの設定時間経過後に、Podに対してSIGTERMシグナルが送られます
  • hard: リミットを超えた直後にPodに対してSIGKILLが送られます

Namespaceリソース

Kubernetesには、複数のNamespaceを作成し、Namespace間をセキュリティ的に分離する機能があります。

1つのNamespaceに複数のDeploymentやServiceを含めることができるので、単純には、Namespaceは他のリソースをまとめるものと捉えても構いません。

Namespaceに対しては、後述するResourceQuotaを使い、Namespaceに含まれるリソース全体に計算資源の制限を掛けることが可能です。

Namespaceの作成

Namespaceは次のコマンドで作成できます。

kubectl create namespace example-namespace

または、以下のようなマニフェストを作成し、

apiVersion: v1
kind: Namespace
metadata:
  name: example-namespace

次のコマンドを実行することでも作成できます。

kubectl create -f example-namespace.yml

Namespaceの指定

kubectlでは-nオプションにより、Namespaceを指定します。

kubectl get pods -n example-namespace

逆に、すべてのNamespaceにまたがった操作を行う場合は、-Aまたは--all-namespacesを指定します。

kubectl get pods -A

ResourceQuota

ResourceQuotaは、Namespaceの計算資源を制限する際に使用します。
以下は旧記法でのResourceQuotaの記述例です。

apiVersion: v1
kind: ResourceQuota
metadata:
  namespace: default
  name: example-quota
spec:
  hard:
    # リソース数の上限値
    pods: 4
    persitentvolumeclaims: 4
    # 計算資源要求合計の上限値
    requests.cpu: "2" # 計算資源要求の合計がこれを超えるとコンテナ起動は拒否されます
    requests.memory: "2Gi"
    requests.storage: "10Gi"
    # 計算資源最大値合計の上限値
    limits.cpu: "8"   # 計算資源最大値の合計がこれを超えるとコンテナ起動は拒否されます
    limits.memory: "8Gi"

ResourceQuotaで設定した項目は、あとで説明するコンテナの計算資源割り当ての際に設定必須になります。コンテナでの設定を忘れると、Podが起動しません。

新記法での記述例です。

apiVersion: v1
kind: ResourceQuota
metadata:
  namespace: default
  name: example-quota
spec:
  hard:
    # リソース数の上限値
    count/pods: 4
    count/persitentvolumeclaims: 4

なお、ResourceQuotaの作成、変更によってその制限に引っ掛かる状態になっても、既に作成済みのリソースは影響を受けず、それらのリソースは動作を継続することができます。

LimitRange

LimitRangeは、個別のNamespaceに対して、Container/Pod/PersistentVolumeClaimに計算資源の制限を掛ける際に使用します。

Container/Podに対するLimitRange

apiVersion: v1
kind: LimitRange
metadata:
  namespace: default
  name: example-container-limit
spec:
  limits:
  - type: Container
    min: # 下限値
      cpu: "125m" # これより小さい計算資源を要求するコンテナ起動は拒否されます
      memory: "128Mi"
    max: # 上限値
      cpu: "2"    # これより大きい計算資源の最大値を設定するコンテナ起動は拒否されます
      memory: "2Gi"
    defaultRequest: # デフォルト値
      cpu: "500m" # 計算資源要求(resources.requests)が設定されていないコンテナのデフォルト値になります
      memory: "512Mi"
    default: # 計算資源要求の上限値
      cpu: "1"    # これより大きい計算資源を要求するコンテナ起動は拒否されます
      memory: "1Gi"
    maxLimitRequestRatio: # 計算資源最大値/要求値の上限値
      cpu: 2      # これより大きい計算資源比のコンテナ起動は拒否されます
      memory: 2

PersistentVolumeClaimに対するLimitRange

apiVersion: v1
kind: LimitRange
metadata:
  namespace: default
  name: example-pvc-limitrange
spec:
  limits:
  - type: PersistentVolumeCraim
    min: # 下限値
      storage: "1Gi" # これより小さい計算資源を要求するコンテナ起動は拒否されます
    max: # 上限値
      storage: "20Gi" # これより大きい計算資源を要求するコンテナ起動は拒否されます

Containerごとの計算資源割り当て

個別のContainerごとに計算資源の割り当てを行う際には、以下のようにDeploymentのresources.requestsresources.limitsを設定します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-app-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: example-app
  template:
    metadata:
      labels:
        app: example-app
    spec:
      containers:
      - name: example-app-container
        image: example-app:v1.0.0-alpha
        resources:
          requests: # このコンテナが使用する計算資源の最小値を設定
            cpu: "500m"     # vCPU数
            memory: "512Mi" # メモリ容量
            ephemeral-storage: "1Gi" # 一時ストレージ容量
          limits:   # このコンテナが使用する計算資源の最大値を設定
            cpu: "1"        # vCPU数(1=1000m)
            memory: "1Gi"   # メモリ容量(1Gi=1024Mi)
            ephemeral-storage: "2Gi"
        ports:
          - containerPort: 3000

vCPUとは仮想コアで、物理的なCPUでは論理コアに等しいです。
例えば、Intel社のCPUでHyper-Threadingが有効化されているものは、

1物理コア = 2論理コア = 2vCPU

となります。

resources.requestsは、コンテナ起動時に割り当てられる計算資源の初期値にもなります。
もし、Kubernetesクラスターを構成するノードにこの初期値を割り当てられるだけの計算資源が余っていないときは、コンテナ起動がスケジューリングされません。

一方resources.limitsでは、ノードにこの最大値を割り当てられるだけの計算資源が余っていない場合でも、コンテナ起動はスケジューリングされます。

なお、LimitRangeにより計算資源の上限が設定されていないNamespaceにあるDeploymentにおいては、resources.limitsを設定しないと、ノードが割り当て可能な上限いっぱいまで資源を消費しようとするので、注意が必要です。

ノードの計算資源が逼迫した状況では、他のPodの動作に重大な悪影響を及ぼします。
例えば、メモリが枯渇するとOut of Memory(OOM)によって、プロセス停止が発生することがあります。

resources.limitsを設定した場合、resources.requestsのデフォルト値はresources.limitsに設定されます。

Ephemeral Storage

ephemeral-storageには、コンテナを再起動すると破棄されてしまう、一時的なストレージの容量を設定します。
Ephemeral Storage容量としてカウントされるものとしては、次の3つがあります。

  • コンテナが出力するログデータ
  • emptyDirに書き込まれたデータ(ただし、medium: Memory指定のemptyDirを除く)
  • コンテナの書き込み可能レイヤーに書き込まれたデータ

なお、emptyDirは複数のコンテナで共有可能なため、例えばコンテナAとBでresources.limits.ephemeral-storage2Giに設定しているとき、emptyDirには合計4Giまで書き込みが可能です。

Podの計算資源割り当て量

Kubernetesでは、複数コンテナを含む1つのPod単位で起動がスケジューリングされるため、個々のコンテナでの計算資源割り当て量から、Podの計算資源割り当て量を計算する必要が出てきます。

Podの計算資源割り当て量は、次の計算式で求められます。
すべてのInitContainerでの最大値と、Containerの計算資源の合計値のうち、どちらか小さくない方

PodのQoS Class

requests, limitsの設定に応じて、Podには次のようなQoS Classが自動で付与されます。

条件 QoS Class
requests, limitsがどちらも未指定 BestEffort
requestsとlimitsの値が同じで、cpuとmemoryの両方が指定済み Guaranteed
Guranteedの条件を満たさず、1つ以上のrequests, limitsが指定済み Burstable

QoS Classはkubectl get podsで確認できます。

kubectl get pods -o custom-columns="Name:{.meta.name},QoS Class:{.status.qosClass}"

Discussion