31 Mar 2020, 09:56

Kubernetesのリソースの基本を今度こそ理解する

こんにちは。もーすけです。
コロナによる在宅勤務を行っている人も多いと思いますが、在宅は捗っていますか? テレビからYouTubeなどを使って、カフェの雑音などを流しておくと家の中でも雰囲気はがらっと変わって集中できたりします。 イヤホンなどで流すよりも部屋全体に流れるようにテレビなどでやるとおすすめです。ぜひトライしてみてください。

今日は、Kubernetesのリソースの基本についてまとめました。
自分自身が理解が不足していたこともありましたし、なんとなくでマニフェストのリソースを書いてハマってしまうことも多くあるなあと思ったからです。 今回書いたリソースは主に3点(コンテナのへのリソース割り当て, QoS Class, LimitRange)ですが、この他にも様々なトピックがあります。機会あれば別にご紹介したいともいます。

コンテナへのリソース割り当て

RequestsとLimits

Kubernetesでは、コンテナ単位でリソース(CPUとメモリー)の割り当て、制限を行うことができます。 リソースの制限について、まずいちばんはじめに理解するべきはRequestsとLimitsです。 例えばPodマニフェストに次のようにspec.containers[].resourcesrequestslimitsを指定することでコンテナへのリソース割り当てが可能です。 requestslimitsが意味することを理解しておくことが重要です。 requestsは使用するリソースの「下限値」を指し、limitsは使用するリソースの「上限値」を指します。

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: wp
    image: wordpress
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
項目 説明
requests 使用するリソースの「下限値」です
limits 使用するリソースの「上限値」です

リソースとPodのスケジューリング

KubernetesがPodをノードにスケジュールするときは、requestsの値を見ます。 下限値なので、下限値を確保できないノードにはスケジュールはもちろんされません。 requestsの値を大きくしすぎると当然ながらスケジュールできなくなる可能性が高まるので注意です。 だからといってrequestsを小さく設定し過ぎることも問題になりえるのでなんとなくで設定してはいけません。(後述します)

requestsとlimitsの大きな差

requestsを小さく設定することも問題になりえるのでなんとなくで設定してはいけません、と書きましたが、小さく設定すること自体が問題ではなく、limitsと大きな差がある場合です。もしrequestsを小さくし、limitsとの大きな差が生まれるとどのようなことが起きるか考えてみましょう。 Podのスケジューリングはrequestsの値を基準に行われるので、多くのPodが1つのノードに集約されます。 そのため、下限値でノードのリソースを埋め尽くす結果となってしまいます。 つまり、requestslimitsに差をつけすぎると、Podがすぐにノードのリソース上限に達してしまい利用したいリソース量が利用できなくなってしまう可能性が高まります。 後で紹介する、LimitRangeでrequestslimitsの比率に制限をかけることができるので、対策も可能です。

上限値を超えた場合

設定したlimitsの上限値を越えるとどうなるのでしょうか。
メモリーが設定したlimitsを超えた場合、oom killerによってPodは強制的に停止します。 limits.memoryはoom killerが発生しない程度に設定する必要があります。
CPUが上限を超える場合は、メモリーの場合とは異なりPodの強制停止はされません。上限以上のCPUを利用できないです。

resourceが未指定の場合

resourceの値が未指定の場合は、基本的には利用できるだけリソースを利用する状態となります。 しかし、LimitRangeでデフォルト値が設定してある場合、未指定でも自動的に設定されます。 利用しているNamespaceにLimitRangeが設定されているかどうか確認しておくと良いです。 また、現実の設定値はkubectl describe pod xxxxで確認するのがよいでしょう。
また、次に説明するQoS ClassでBestEffortとなるため、ノードのメモリーが不足した場合は最優先に停止対象のPodとして選ばれます。

QoS class

上で説明した、requestslimitsの設定によってKubernetesは、QoS Classという値をPodに設定します。 この値は、自動的に付与されるものでユーザ側で任意に設定するものではありません。 QoS Classは、ノードのリソースの上限などでKubernetesがPodを強制停止する際の対象を選定する際に利用されるものです。
QoS Classは以下の条件によって決定します。

QoS Class 設定条件 優先度
BestEffort requests, limitsともに未指定のとき 3
Burstable 1つ以上のrequestsかlimitsが設定されているとき(Guaranteedではない) 2
Guaranteed requestsとlimitsが同じ値でCPU・メモリーともに設定されている 1

例えば、ノードのメモリー上限に達すると(コンテナのlimitsの上限ではなく)、KubernetesはPodを強制的に退去(Evict)しリソースを確保します。その際に、QoS Classを参照します。BestEffortから順にPodを停止する動きを取ります。
実際に設定されているQoS Classを確認するには稼働しているPodをdescribeで見てみるとわかります。

$ kubectl describe pod pod-name
...
Volumes:
  default-token-xcrmt:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-xcrmt
    Optional:    false
QoS Class:       Burstable
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/memory-pressure:NoSchedule
                 node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:          <none>

同じクラスだった場合

メモリーがノードの上限に達し、oom killerによってPodを強制停止をする場合にQoS Classによって停止するPodの優先度が決定すると書きましたが、同じクラスだった場合はスコアによってきまります。 以下のKubernetesの公式ドキュメントに記載があるのですが、Busrstableの場合、Podのrequestしたメモリー量とノードのキャパシティの割合によってスコア付けされます。

Configure Out of Resource Handling - Kubernetes

使いみち

仕組みは理解できたとして、このQoSをどう使っていけばいいのでしょうか。
すべてを十分なリソースでGuaranteedにできるに越したことはないですが、 確保した量と実際に使用する量に乖離がある場合もありますし、Podの収容効率もさがります。 Webサーバのように複数台で分散可能で、一部のPodが停止しても大きな影響がないものはBurstableで、JobやStatefulなアプリケーションなどはGuaranteedを検討すると良いのではないかと思います。 本番環境で動くアプリケーションではBusrstableはそもそもおすすめしません。

LimitRange

LimitRangeというKubernetesリソースがあります。 こちらを利用することで以下が設定できます。
(公式ドキュメント: Limit Ranges - Kubernetes)

  1. デフォルトのLimitsの設定
  2. デフォルトのRequestsの設定
  3. 最大のリソース使用量の設定
  4. 最小のリソース使用量の設定
  5. LimitsとRequestsの割合の設定

このLimitRangeを設定することで上で一部注意事項として書いたことの対策ができるようになります。
具体的には以下のようなことが対策できるようになります。

  1. BestEffortのQoS Classが設定されたPodを作らない
  2. LimitsとRequestsの大幅な差をさくす
  3. 過剰なリソースの要求をさせない

設定とデバッグ

LimitRangeはnamespace毎に設定できるものです。 また、PodとContainerに対して設定が可能です。
設定方法は極めて簡単で、以下のようなマニフェストを適応するのみです。

apiVersion: "v1"
kind: "LimitRange"
metadata:
  name: "core-resource-limits"
spec:
  limits:
    - type: "Pod"
      max:
        cpu: "2"
        memory: "1Gi"
      min:
        cpu: "200m"
        memory: "6Mi"
    - type: "Container"
      max:
        cpu: "2"
        memory: "1Gi"
      min:
        cpu: "100m"
        memory: "4Mi"
      default:
        cpu: "300m"
        memory: "200Mi"
      defaultRequest:
        cpu: "200m"
        memory: "100Mi"
      maxLimitRequestRatio:
        cpu: "2"
        memory: "2"

例えば、この場合maxLimitRequestRatio.memoryが “2” に設定されており、メモリーのrequestsとlimitsの比率が2倍までとしています。この状態で、2倍以上の差のあるDeploymentをデプロイしてみます。

deploymentはできていますが、Podが起動してきません。 この場合、deploymentではなく、replicasetのeventを見ると良いです。 実際に見るとメモリーのrequestsとlimitsの比率が2までの制限がかかっているが実際には4ほどある、という旨のエラーメッセージを確認できました。

$ kubectl get deployment
NAME          READY   UP-TO-DATE   AVAILABLE   AGE
debug-nginx   0/1     0            0           2m11s

$ kubectl get replicaset
NAME                     DESIRED   CURRENT   READY   AGE
debug-nginx-6d7d6b87b9   1         0         0       93s

$ kubectl describe replicaset debug-nginx-6d7d6b87b9
...
Events:
  Type     Reason        Age                From                   Message
  ----     ------        ----               ----                   -------
  Warning  FailedCreate  31s                replicaset-controller  Error creating: pods "debug-nginx-6d7d6b87b9-qplmn" is forbidden: [maximum memory usage per Pod is 1Gi, but
limit is 2147483648, maximum memory usage per Container is 1Gi, but limit is 2Gi, memory max limit to request ratio per Container is 2, but provided ratio is 4.096000]

まとめ

Kubernetesにおけるリソースの基本となる3つの項目について見てきました。
最低限このくらいのことがわかっているとデバッグ作業に非常に役立つと感じています。 なんとなくで設定していた方は今日で卒業してもらえればと思います。

Kubernetesのリソースは、今日紹介したことの他にもNamespaceごとのリソースのクオータ制限をするResourceQuotaや、Podのオートスケーリング、スケジューリングなどなど非常に奥が深いです。 より体系的に深く学びたい方は、ぜひともKubernetes完全ガイドをおすすめします。我らのバイブルです。

関連する記事はこちら
  • TektonのOperatorによるインストールとHello World (2020/05/10)
  • イメージのプルの効率化を考える。kubernetes-image-puller の紹介 (2020/04/28)
  • Jenkins pipelineで複数のOpenShiftクラスタを扱う (2020/04/14)
  • Kubernetes、namespaceの分け方に迷った場合に考えること (2020/04/09)
  • Skopeoを利用したコンテナイメージのレジストリ間コピー (2020/04/05)
  • comments powered by Disqus
    このエントリーをはてなブックマークに追加