This the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

スケジューリングと退避

Kubernetesにおいてスケジューリングとは、稼働させたいPodをNodeにマッチさせ、kubeletが実行できるようにすることを指します。 退避とは、リソース不足のNodeで1つ以上のPodを積極的に停止させるプロセスです。

1 - Node上へのPodのスケジューリング

Podが稼働するNodeを特定のものに指定したり、優先条件を指定して制限することができます。 これを実現するためにはいくつかの方法がありますが、推奨されている方法はラベルでの選択です。 スケジューラーが最適な配置を選択するため、一般的にはこのような制限は不要です(例えば、複数のPodを別々のNodeへデプロイしたり、Podを配置する際にリソースが不十分なNodeにはデプロイされないことが挙げられます)が、 SSDが搭載されているNodeにPodをデプロイしたり、同じアベイラビリティーゾーン内で通信する異なるサービスのPodを同じNodeにデプロイする等、柔軟な制御が必要なこともあります。

nodeSelector

nodeSelectorは、Nodeを選択するための、最も簡単で推奨されている手法です。 nodeSelectorはPodSpecのフィールドです。これはkey-valueペアのマップを特定します。 あるノードでPodを稼働させるためには、そのノードがラベルとして指定されたkey-valueペアを保持している必要があります(複数のラベルを保持することも可能です)。 最も一般的な使用方法は、1つのkey-valueペアを付与する方法です。

以下に、nodeSelectorの使用例を紹介します。

ステップ0: 前提条件

この例では、KubernetesのPodに関して基本的な知識を有していることと、Kubernetesクラスターのセットアップがされていることが前提となっています。

ステップ1: Nodeへのラベルの付与

kubectl get nodesで、クラスターのノードの名前を取得してください。 そして、ラベルを付与するNodeを選び、kubectl label nodes <node-name> <label-key>=<label-value>で選択したNodeにラベルを付与します。 例えば、Nodeの名前が'kubernetes-foo-node-1.c.a-robinson.internal'、付与するラベルが'disktype=ssd'の場合、kubectl label nodes kubernetes-foo-node-1.c.a-robinson.internal disktype=ssdによってラベルが付与されます。

kubectl get nodes --show-labelsによって、ノードにラベルが付与されたかを確認することができます。 また、kubectl describe node "nodename"から、そのNodeの全てのラベルを表示することもできます。

ステップ2: PodへのnodeSelectorフィールドの追加

該当のPodのconfigファイルに、nodeSelectorのセクションを追加します: 例として以下のconfigファイルを扱います:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx

nodeSelectorを以下のように追加します:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd

kubectl apply -f https://k8s.io/examples/pods/pod-nginx.yamlにより、Podは先ほどラベルを付与したNodeへスケジュールされます。 kubectl get pods -o wideで表示される"NODE"の列から、PodがデプロイされているNodeを確認することができます。

補足: ビルトインNodeラベル

明示的に付与するラベルの他に、事前にNodeへ付与されているものもあります。 以下のようなラベルが該当します。

備考: これらのラベルは、クラウドプロバイダー固有であり、確実なものではありません。 例えば、kubernetes.io/hostnameの値はNodeの名前と同じである環境もあれば、異なる環境もあります。

Nodeの隔離や制限

Nodeにラベルを付与することで、Podは特定のNodeやNodeグループにスケジュールされます。 これにより、特定のPodを、確かな隔離性や安全性、特性を持ったNodeで稼働させることができます。 この目的でラベルを使用する際に、Node上のkubeletプロセスに上書きされないラベルキーを選択することが強く推奨されています。 これは、安全性が損なわれたNodeがkubeletの認証情報をNodeのオブジェクトに設定したり、スケジューラーがそのようなNodeにデプロイすることを防ぎます。

NodeRestrictionプラグインは、kubeletがnode-restriction.kubernetes.io/プレフィックスを有するラベルの設定や上書きを防ぎます。 Nodeの隔離にラベルのプレフィックスを使用するためには、以下のようにします。

  1. Node authorizerを使用していることと、NodeRestriction admission pluginが_有効_になっていること。
  2. Nodeにnode-restriction.kubernetes.io/ プレフィックスのラベルを付与し、そのラベルがnode selectorに指定されていること。 例えば、example.com.node-restriction.kubernetes.io/fips=true または example.com.node-restriction.kubernetes.io/pci-dss=trueのようなラベルです。

アフィニティとアンチアフィニティ

nodeSelectorはPodの稼働を特定のラベルが付与されたNodeに制限する最も簡単な方法です。 アフィニティ/アンチアフィニティでは、より柔軟な指定方法が提供されています。 拡張機能は以下の通りです。

  1. アフィニティ/アンチアフィニティという用語はとても表現豊かです。この用語は論理AND演算で作成された完全一致だけではなく、より多くのマッチングルールを提供します。
  2. 必須条件ではなく優先条件を指定でき、条件を満たさない場合でもPodをスケジュールさせることができます。
  3. Node自体のラベルではなく、Node(または他のトポロジカルドメイン)上で稼働している他のPodのラベルに対して条件を指定することができ、そのPodと同じ、または異なるドメインで稼働させることができます。

アフィニティは"Nodeアフィニティ"と"Pod間アフィニティ/アンチアフィニティ"の2種類から成ります。 NodeアフィニティはnodeSelector(前述の2つのメリットがあります)に似ていますが、Pod間アフィニティ/アンチアフィニティは、上記の3番目の機能に記載している通り、NodeのラベルではなくPodのラベルに対して制限をかけます。

Nodeアフィニティ

Nodeアフィニティは概念的には、NodeのラベルによってPodがどのNodeにスケジュールされるかを制限するnodeSelectorと同様です。

現在は2種類のNodeアフィニティがあり、requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecutionです。 前者はNodeにスケジュールされるPodが条件を満たすことが必須(nodeSelectorに似ていますが、より柔軟に条件を指定できます)であり、後者は条件を指定できますが保証されるわけではなく、優先的に考慮されます。 "IgnoredDuringExecution"の意味するところは、nodeSelectorの機能と同様であり、Nodeのラベルが変更され、Podがその条件を満たさなくなった場合でも PodはそのNodeで稼働し続けるということです。 将来的には、requiredDuringSchedulingIgnoredDuringExecutionに、PodのNodeアフィニティに記された必須要件を満たさなくなったNodeからそのPodを退避させることができる機能を備えたrequiredDuringSchedulingRequiredDuringExecutionが提供される予定です。

それぞれの使用例として、 requiredDuringSchedulingIgnoredDuringExecution は、"インテルCPUを供えたNode上でPodを稼働させる"、 preferredDuringSchedulingIgnoredDuringExecutionは、"ゾーンXYZでPodの稼働を試みますが、実現不可能な場合には他の場所で稼働させる" といった方法が挙げられます。

Nodeアフィニティは、PodSpecのaffinityフィールドにあるnodeAffinityフィールドで特定します。

Nodeアフィニティを使用したPodの例を以下に示します:

apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/e2e-az-name
            operator: In
            values:
            - e2e-az1
            - e2e-az2
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:2.0

このNodeアフィニティでは、Podはキーがkubernetes.io/e2e-az-name、値がe2e-az1またはe2e-az2のラベルが付与されたNodeにしか配置されません。 加えて、キーがanother-node-label-key、値がanother-node-label-valueのラベルが付与されたNodeが優先されます。

この例ではオペレーターInが使われています。 Nodeアフィニティでは、InNotInExistsDoesNotExistGtLtのオペレーターが使用できます。 NotInDoesNotExistはNodeアンチアフィニティ、またはPodを特定のNodeにスケジュールさせない場合に使われるTaintsに使用します。

nodeSelectornodeAffinityの両方を指定した場合、Podは両方の条件を満たすNodeにスケジュールされます。

nodeAffinity内で複数のnodeSelectorTermsを指定した場合、PodはいずれかのnodeSelectorTermsを満たしたNodeへスケジュールされます。

nodeSelectorTerms内で複数のmatchExpressionsを指定した場合にはPodは全てのmatchExpressionsを満たしたNodeへスケジュールされます。

PodがスケジュールされたNodeのラベルを削除したり変更しても、Podは削除されません。 言い換えると、アフィニティはPodをスケジュールする際にのみ考慮されます。

preferredDuringSchedulingIgnoredDuringExecution内のweightフィールドは、1から100の範囲で指定します。 全ての必要条件(リソースやRequiredDuringSchedulingアフィニティ等)を満たしたNodeに対して、スケジューラーはそのNodeがMatchExpressionsを満たした場合に、このフィルードの"weight"を加算して合計を計算します。 このスコアがNodeの他の優先機能のスコアと組み合わせれ、最も高いスコアを有したNodeが優先されます。

Pod間アフィニティとアンチアフィニティ

Pod間アフィニティとアンチアフィニティは、Nodeのラベルではなく、すでにNodeで稼働しているPodのラベルに従ってPodがスケジュールされるNodeを制限します。 このポリシーは、"XにてルールYを満たすPodがすでに稼働している場合、このPodもXで稼働させる(アンチアフィニティの場合は稼働させない)"という形式です。 Yはnamespaceのリストで指定したLabelSelectorで表されます。 Nodeと異なり、Podはnamespaceで区切られているため(それゆえPodのラベルも暗黙的にnamespaceで区切られます)、Podのラベルを指定するlabel selectorは、どのnamespaceにselectorを適用するかを指定する必要があります。 概念的に、XはNodeや、ラック、クラウドプロバイダゾーン、クラウドプロバイダのリージョン等を表すトポロジードメインです。 これらを表すためにシステムが使用するNodeラベルのキーであるtopologyKeyを使うことで、トポロジードメインを指定することができます。 先述のセクション補足: ビルトインNodeラベルにてラベルの例が紹介されています。

備考: Pod間アフィニティとアンチアフィニティは、大規模なクラスター上で使用する際にスケジューリングを非常に遅くする恐れのある多くの処理を要します。 そのため、数百台以上のNodeから成るクラスターでは使用することを推奨されません。
備考: Podのアンチアフィニティは、Nodeに必ずラベルが付与されている必要があります。 言い換えると、クラスターの全てのNodeが、topologyKeyで指定されたものに合致する適切なラベルが必要になります。 それらが付与されていないNodeが存在する場合、意図しない挙動を示すことがあります。

Nodeアフィニティと同様に、PodアフィニティとPodアンチアフィニティにも必須条件と優先条件を示すrequiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecutionがあります。 前述のNodeアフィニティのセクションを参照してください。 requiredDuringSchedulingIgnoredDuringExecutionを指定するアフィニティの使用例は、"Service AのPodとService BのPodが密に通信する際、それらを同じゾーンで稼働させる場合"です。 また、preferredDuringSchedulingIgnoredDuringExecutionを指定するアンチアフィニティの使用例は、"ゾーンをまたいでPodのサービスを稼働させる場合"(Podの数はゾーンの数よりも多いため、必須条件を指定すると合理的ではありません)です。

Pod間アフィニティは、PodSpecのaffinityフィールド内にpodAffinityで指定し、Pod間アンチアフィニティは、podAntiAffinityで指定します。

Podアフィニティを使用したPodの例

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: topology.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: kubernetes.io/hostname
  containers:
  - name: with-pod-affinity
    image: k8s.gcr.io/pause:2.0

このPodのアフィニティは、PodアフィニティとPodアンチアフィニティを1つずつ定義しています。 この例では、podAffinityrequiredDuringSchedulingIgnoredDuringExecutionpodAntiAffinitypreferredDuringSchedulingIgnoredDuringExecutionが設定されています。 Podアフィニティは、「キーが"security"、値が"S1"のラベルが付与されたPodが少なくとも1つは稼働しているNodeが同じゾーンにあれば、PodはそのNodeにスケジュールされる」という条件を指定しています(より正確には、キーが"security"、値が"S1"のラベルが付与されたPodが稼働しており、キーがtopology.kubernetes.io/zone、値がVであるNodeが少なくとも1つはある状態で、 Node Nがキーtopology.kubernetes.io/zone、値Vのラベルを持つ場合に、PodはNode Nで稼働させることができます)。 Podアンチアフィニティは、「すでにあるNode上で、キーが"security"、値が"S2"であるPodが稼働している場合に、Podを可能な限りそのNode上で稼働させない」という条件を指定しています (topologyKeytopology.kubernetes.io/zoneであった場合、キーが"security"、値が"S2"であるであるPodが稼働しているゾーンと同じゾーン内のNodeにはスケジュールされなくなります)。 PodアフィニティとPodアンチアフィニティや、requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecutionに関する他の使用例はデザインドックを参照してください。

PodアフィニティとPodアンチアフィニティで使用できるオペレーターは、InNotInExistsDoesNotExistです。

原則として、topologyKeyには任意のラベルとキーが使用できます。 しかし、パフォーマンスやセキュリティの観点から、以下の制約があります:

  1. アフィニティと、requiredDuringSchedulingIgnoredDuringExecutionを指定したPodアンチアフィニティは、topologyKeyを指定しないことは許可されていません。
  2. requiredDuringSchedulingIgnoredDuringExecutionを指定したPodアンチアフィニティでは、kubernetes.io/hostnametopologyKeyを制限するため、アドミッションコントローラーLimitPodHardAntiAffinityTopologyが導入されました。 トポロジーをカスタマイズする場合には、アドミッションコントローラーを修正または無効化する必要があります。
  3. preferredDuringSchedulingIgnoredDuringExecutionを指定したPodアンチアフィニティでは、topologyKeyを省略することはできません。
  4. 上記の場合を除き、topologyKey は任意のラベルとキーを指定することができます。

labelSelectortopologyKeyに加え、labelSelectorが合致すべきnamespacesのリストを特定することも可能です(これはlabelSelectortopologyKeyを定義することと同等です)。 省略した場合や空の場合は、アフィニティとアンチアフィニティが定義されたPodのnamespaceがデフォルトで設定されます。

requiredDuringSchedulingIgnoredDuringExecutionが指定されたアフィニティとアンチアフィニティでは、matchExpressionsに記載された全ての条件が満たされるNodeにPodがスケジュールされます。

実際的なユースケース

Pod間アフィニティとアンチアフィニティは、ReplicaSet、StatefulSet、Deploymentなどのより高レベルなコレクションと併せて使用するとさらに有用です。 Workloadが、Node等の定義された同じトポロジーに共存させるよう、簡単に設定できます。

常に同じNodeで稼働させる場合

3つのノードから成るクラスターでは、ウェブアプリケーションはredisのようにインメモリキャッシュを保持しています。 このような場合、ウェブサーバーは可能な限りキャッシュと共存させることが望ましいです。

ラベルapp=storeを付与した3つのレプリカから成るredisのdeploymentを記述したyamlファイルを示します。 Deploymentには、1つのNodeにレプリカを共存させないためにPodAntiAffinityを付与しています。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: redis-server
        image: redis:3.2-alpine

ウェブサーバーのDeploymentを記載した以下のyamlファイルには、podAntiAffinitypodAffinityが設定されています。 全てのレプリカがapp=storeのラベルが付与されたPodと同じゾーンで稼働するよう、スケジューラーに設定されます。 また、それぞれのウェブサーバーは1つのノードで稼働されないことも保証されます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 3
  template:
    metadata:
      labels:
        app: web-store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-store
            topologyKey: "kubernetes.io/hostname"
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: nginx:1.16-alpine

上記2つのDeploymentが生成されると、3つのノードは以下のようになります。

node-1node-2node-3
webserver-1webserver-2webserver-3
cache-1cache-2cache-3

このように、3つのweb-serverは期待通り自動的にキャッシュと共存しています。

kubectl get pods -o wide

出力は以下のようになります:

NAME                           READY     STATUS    RESTARTS   AGE       IP           NODE
redis-cache-1450370735-6dzlj   1/1       Running   0          8m        10.192.4.2   kube-node-3
redis-cache-1450370735-j2j96   1/1       Running   0          8m        10.192.2.2   kube-node-1
redis-cache-1450370735-z73mh   1/1       Running   0          8m        10.192.3.1   kube-node-2
web-server-1287567482-5d4dz    1/1       Running   0          7m        10.192.2.3   kube-node-1
web-server-1287567482-6f7v5    1/1       Running   0          7m        10.192.4.3   kube-node-3
web-server-1287567482-s330j    1/1       Running   0          7m        10.192.3.2   kube-node-2
同じNodeに共存させない場合

上記の例では PodAntiAffinitytopologyKey: "kubernetes.io/hostname"と合わせて指定することで、redisクラスター内の2つのインスタンスが同じホストにデプロイされない場合を扱いました。 同様の方法で、アンチアフィニティを用いて高可用性を実現したStatefulSetの使用例はZooKeeper tutorialを参照してください。

nodeName

nodeNameはNodeの選択を制限する最も簡単な方法ですが、制約があることからあまり使用されません。 nodeNameはPodSpecのフィールドです。 ここに値が設定されると、schedulerはそのPodを考慮しなくなり、その名前が付与されているNodeのkubeletはPodを稼働させようとします。 そのため、PodSpecにnodeNameが指定されると、上述のNodeの選択方法よりも優先されます。

nodeNameを使用することによる制約は以下の通りです:

  • その名前のNodeが存在しない場合、Podは起動されす、自動的に削除される場合があります。
  • その名前のNodeにPodを稼働させるためのリソースがない場合、Podの起動は失敗し、理由は例えばOutOfmemoryやOutOfcpuになります。
  • クラウド上のNodeの名前は予期できず、変更される可能性があります。

nodeNameを指定したPodの設定ファイルの例を示します:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  nodeName: kube-01

上記のPodはkube-01という名前のNodeで稼働します。

次の項目

Taintsを使うことで、NodeはPodを追い出すことができます。

NodeアフィニティPod間アフィニティ/アンチアフィニティ のデザインドキュメントには、これらの機能の追加のバックグラウンドの情報が記載されています。

一度PodがNodeに割り当たると、kubeletはPodを起動してノード内のリソースを確保します。 トポロジーマネージャーはNodeレベルのリソース割り当てを決定する際に関与します。

2 - TaintとToleration

NodeアフィニティPodの属性であり、あるNode群を引きつけます(優先条件または必須条件)。反対に taint はNodeがある種のPodを排除できるようにします。

toleration はPodに適用され、一致するtaintが付与されたNodeへPodがスケジューリングされることを認めるものです。ただしそのNodeへ必ずスケジューリングされるとは限りません。

taintとtolerationは組になって機能し、Podが不適切なNodeへスケジューリングされないことを保証します。taintはNodeに一つまたは複数個付与することができます。これはそのNodeがtaintを許容しないPodを受け入れるべきではないことを示します。

コンセプト

Nodeにtaintを付与するにはkubectl taintコマンドを使用します。 例えば、次のコマンドは

kubectl taint nodes node1 key=value:NoSchedule

node1にtaintを設定します。このtaintのキーはkey、値はvalue、taintの効果はNoScheduleです。 これはnode1にはPodに合致するtolerationがなければスケジューリングされないことを意味します。

上記のコマンドで付与したtaintを外すには、下記のコマンドを使います。

kubectl taint nodes node1 key:NoSchedule-

PodのtolerationはPodSpecの中に指定します。下記のtolerationはどちらも、上記のkubectl taintコマンドで追加したtaintと合致するため、どちらのtolerationが設定されたPodもnode1へスケジューリングされることができます。

tolerations:
- key: "key"
  operator: "Equal"
  value: "value"
  effect: "NoSchedule"
tolerations:
- key: "key"
  operator: "Exists"
  effect: "NoSchedule"

tolerationを設定したPodの例を示します。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "example-key"
    operator: "Exists"
    effect: "NoSchedule"

operatorのデフォルトはEqualです。

tolerationがtaintと合致するのは、keyeffectが同一であり、さらに下記の条件のいずれかを満たす場合です。

  • operatorExistsvalueを指定すべきでない場合)
  • operatorEqualであり、かつvalueが同一である場合
備考:

2つ特殊な場合があります。

空のkeyと演算子Existsは全てのkeyvalueeffectと一致するため、すべてのtaintと合致します。

空のeffectkeyが一致する全てのeffectと合致します。

上記の例ではeffectNoScheduleを指定しました。代わりに、effectPreferNoScheduleを指定することができます。 これはNoScheduleの「ソフトな」バージョンであり、システムはtaintに対応するtolerationが設定されていないPodがNodeへ配置されることを避けようとしますが、必須の条件とはしません。3つ目のeffectの値としてNoExecuteがありますが、これについては後述します。

同一のNodeに複数のtaintを付与することや、同一のPodに複数のtolerationを設定することができます。 複数のtaintやtolerationが設定されている場合、Kubernetesはフィルタのように扱います。最初はNodeの全てのtaintがある状態から始め、Podが対応するtolerationを持っているtaintは無視され外されていきます。無視されずに残ったtaintが効果を及ぼします。 具体的には、

  • effect NoScheduleのtaintが無視されず残った場合、KubernetesはそのPodをNodeへスケジューリングしません。
  • effect NoScheduleのtaintは残らず、effect PreferNoScheduleのtaintは残った場合、KubernetesはそのNodeへのスケジューリングをしないように試みます。
  • effect NoExecuteのtaintが残った場合、既に稼働中のPodはそのNodeから排除され、まだ稼働していないPodはスケジューリングされないようになります。

例として、下記のようなtaintが付与されたNodeを考えます。

kubectl taint nodes node1 key1=value1:NoSchedule
kubectl taint nodes node1 key1=value1:NoExecute
kubectl taint nodes node1 key2=value2:NoSchedule

Podには2つのtolerationが設定されています。

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"

この例では、3つ目のtaintと合致するtolerationがないため、PodはNodeへはスケジューリングされません。 しかし、これらのtaintが追加された時点で、そのNodeでPodが稼働していれば続けて稼働することが可能です。 これは、Podのtolerationと合致しないtaintは3つあるtaintのうちの3つ目のtaintのみであり、それがNoScheduleであるためです。

一般に、effect NoExecuteのtaintがNodeに追加されると、合致するtolerationが設定されていないPodは即時にNodeから排除され、合致するtolerationが設定されたPodが排除されることは決してありません。 しかし、effectNoExecuteに対するtolerationはtolerationSecondsフィールドを任意で指定することができ、これはtaintが追加された後にそのNodeにPodが残る時間を示します。例えば、

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"
  tolerationSeconds: 3600

この例のPodが稼働中で、対応するtaintがNodeへ追加された場合、PodはそのNodeに3600秒残り、その後排除されます。仮にtaintがそれよりも前に外された場合、Podは排除されません。

ユースケースの例

taintとtolerationは、実行されるべきではないNodeからPodを遠ざけたり、排除したりするための柔軟な方法です。いくつかのユースケースを示します。

  • 専有Node: あるNode群を特定のユーザーに専有させたい場合、そのNode群へtaintを追加し(kubectl taint nodes nodename dedicated=groupName:NoSchedule) 対応するtolerationをPodへ追加します(これを実現する最も容易な方法はカスタム アドミッションコントローラーを書くことです)。 tolerationが設定されたPodはtaintの設定された(専有の)Nodeと、クラスターにあるその他のNodeの使用が認められます。もしPodが必ず専有Nodeのみを使うようにしたい場合は、taintと同様のラベルをそのNode群に設定し(例: dedicated=groupName)、アドミッションコントローラーはNodeアフィニティを使ってPodがdedicated=groupNameのラベルの付いたNodeへスケジューリングすることが必要であるということも設定する必要があります。

  • 特殊なハードウェアを備えるNode: クラスターの中の少数のNodeが特殊なハードウェア(例えばGPU)を備える場合、そのハードウェアを必要としないPodがスケジューリングされないようにして、後でハードウェアを必要とするPodができたときの余裕を確保したいことがあります。 これは特殊なハードウェアを持つNodeにtaintを追加(例えば kubectl taint nodes nodename special=true:NoSchedule または kubectl taint nodes nodename special=true:PreferNoSchedule)して、ハードウェアを使用するPodに対応するtolerationを追加することで可能です。 専有Nodeのユースケースと同様に、tolerationを容易に適用する方法はカスタム アドミッションコントローラーを使うことです。 例えば、特殊なハードウェアを表すために拡張リソース を使い、ハードウェアを備えるNodeに拡張リソースの名称のtaintを追加して、 拡張リソースtoleration アドミッションコントローラーを実行することが推奨されます。Nodeにはtaintが付与されているため、tolerationのないPodはスケジューリングされません。しかし拡張リソースを要求するPodを作成しようとすると、拡張リソースtoleration アドミッションコントローラーはPodに自動的に適切なtolerationを設定し、Podはハードウェアを備えるNodeへスケジューリングされます。 これは特殊なハードウェアを備えたNodeではそれを必要とするPodのみが稼働し、Podに対して手作業でtolerationを追加しなくて済むようにします。

  • taintを基にした排除: Nodeに問題が起きたときにPodごとに排除する設定を行うことができます。次のセクションにて説明します。

taintを基にした排除

FEATURE STATE: Kubernetes v1.18 [stable]

上述したように、effect NoExecuteのtaintはNodeで実行中のPodに次のような影響を与えます。

  • 対応するtolerationのないPodは即座に除外される
  • 対応するtolerationがあり、それにtolerationSecondsが指定されていないPodは残り続ける
  • 対応するtolerationがあり、それにtolerationSecondsが指定されているPodは指定された間、残される

Nodeコントローラーは特定の条件を満たす場合に自動的にtaintを追加します。 組み込まれているtaintは下記の通りです。

  • node.kubernetes.io/not-ready: Nodeの準備ができていない場合。これはNodeCondition ReadyFalseである場合に対応します。
  • node.kubernetes.io/unreachable: NodeがNodeコントローラーから到達できない場合。これはNodeConditionReadyUnknownの場合に対応します。
  • node.kubernetes.io/out-of-disk: Nodeのディスクの空きがない場合。
  • node.kubernetes.io/memory-pressure: Nodeのメモリーが不足している場合。
  • node.kubernetes.io/disk-pressure: Nodeのディスクが不足している場合。
  • node.kubernetes.io/network-unavailable: Nodeのネットワークが利用できない場合。
  • node.kubernetes.io/unschedulable: Nodeがスケジューリングできない場合。
  • node.cloudprovider.kubernetes.io/uninitialized: kubeletが外部のクラウド事業者により起動されたときに設定されるtaintで、このNodeは利用不可能であることを示します。cloud-controller-managerによるコントローラーがこのNodeを初期化した後にkubeletはこのtaintを外します。

Nodeから追い出すときには、Nodeコントローラーまたはkubeletは関連するtaintをNoExecute効果の状態で追加します。 不具合のある状態から通常の状態へ復帰した場合は、kubeletまたはNodeコントローラーは関連するtaintを外すことができます。

備考: コントロールプレーンは新しいtaintをNodeに加えるレートを制限しています。 このレート制限は一度に多くのNodeが到達不可能になった場合(例えばネットワークの断絶)に、退役させられるNodeの数を制御します。

PodにtolerationSecondsを指定することで不具合があるか応答のないNodeに残る時間を指定することができます。

例えば、ローカルの状態を多数持つアプリケーションとネットワークが分断された場合を考えます。ネットワークが復旧して、Podを排除しなくて済むことを見込んで、長時間Nodeから排除されないようにしたいこともあるでしょう。 この場合Podに設定するtolerationは次のようになります。

tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 6000
備考:

Kubernetesはユーザーまたはコントローラーが明示的に指定しない限り、自動的にnode.kubernetes.io/not-readynode.kubernetes.io/unreachableに対するtolerationをtolerationSeconds=300にて設定します。

自動的に設定されるtolerationは、taintに対応する問題がNodeで検知されても5分間はそのNodeにPodが残されることを意味します。

DaemonSetのPodは次のtaintに対してNoExecuteのtolerationがtolerationSecondsを指定せずに設定されます。

  • node.kubernetes.io/unreachable
  • node.kubernetes.io/not-ready

これはDaemonSetのPodはこれらの問題によって排除されないことを保証します。

条件によるtaintの付与

NodeのライフサイクルコントローラーはNodeの状態に応じてNoSchedule効果のtaintを付与します。 スケジューラーはNodeの状態ではなく、taintを確認します。 Nodeに何がスケジューリングされるかは、そのNodeの状態に影響されないことを保証します。ユーザーは適切なtolerationをPodに付与することで、どの種類のNodeの問題を無視するかを選ぶことができます。

DaemonSetのコントローラーは、DaemonSetが中断されるのを防ぐために自動的に次のNoScheduletolerationを全てのDaemonSetに付与します。

  • node.kubernetes.io/memory-pressure
  • node.kubernetes.io/disk-pressure
  • node.kubernetes.io/out-of-disk (重要なPodのみ)
  • node.kubernetes.io/unschedulable (1.10またはそれ以降)
  • node.kubernetes.io/network-unavailable (ホストネットワークのみ)

これらのtolerationを追加することは後方互換性を保証します。DaemonSetに任意のtolerationを加えることもできます。

次の項目

3 - Kubernetesのスケジューラー

Kubernetesにおいて、スケジューリング とは、KubeletPodを稼働させるためにNodeに割り当てることを意味します。

スケジューリングの概要

スケジューラーは新規に作成されたPodで、Nodeに割り当てられていないものを監視します。スケジューラーは発見した各Podのために、稼働させるべき最適なNodeを見つけ出す責務を担っています。そのスケジューラーは下記で説明するスケジューリングの原理を考慮に入れて、NodeへのPodの割り当てを行います。

Podが特定のNodeに割り当てられる理由を理解したい場合や、カスタムスケジューラーを自身で作ろうと考えている場合、このページはスケジューリングに関して学ぶのに役立ちます。

kube-scheduler

kube-schedulerはKubernetesにおけるデフォルトのスケジューラーで、コントロールプレーンの一部分として稼働します。 kube-schedulerは、もし希望するのであれば自分自身でスケジューリングのコンポーネントを実装でき、それを代わりに使用できるように設計されています。

kube-schedulerは、新規に作成された各Podや他のスケジューリングされていないPodを稼働させるために最適なNodeを選択します。 しかし、Pod内の各コンテナにはそれぞれ異なるリソースの要件があり、各Pod自体にもそれぞれ異なる要件があります。そのため、既存のNodeは特定のスケジューリング要求によってフィルターされる必要があります。

クラスター内でPodに対する割り当て要求を満たしたNodeは_割り当て可能_ なNodeと呼ばれます。 もし適切なNodeが一つもない場合、スケジューラーがNodeを割り当てることができるまで、そのPodはスケジュールされずに残ります。

スケジューラーはPodに対する割り当て可能なNodeをみつけ、それらの割り当て可能なNodeにスコアをつけます。その中から最も高いスコアのNodeを選択し、Podに割り当てるためのいくつかの関数を実行します。 スケジューラーは_binding_ と呼ばれる処理中において、APIサーバーに対して割り当てが決まったNodeの情報を通知します。

スケジューリングを決定する上で考慮が必要な要素としては、個別または複数のリソース要求や、ハードウェア/ソフトウェアのポリシー制約、affinityやanti-affinityの設定、データの局所性や、ワークロード間での干渉などが挙げられます。

kube-schedulerによるスケジューリング

kube-schedulerは2ステップの操作によってPodに割り当てるNodeを選択します。

  1. フィルタリング

  2. スコアリング

フィルタリング ステップでは、Podに割り当て可能なNodeのセットを探します。例えばPodFitsResourcesフィルターは、Podのリソース要求を満たすのに十分なリソースをもつNodeがどれかをチェックします。このステップの後、候補のNodeのリストは、要求を満たすNodeを含みます。 たいてい、リストの要素は複数となります。もしこのリストが空の場合、そのPodはスケジュール可能な状態とはなりません。

スコアリング ステップでは、Podを割り当てるのに最も適したNodeを選択するために、スケジューラーはリストの中のNodeをランク付けします。 スケジューラーは、フィルタリングによって選ばれた各Nodeに対してスコアを付けます。このスコアはアクティブなスコア付けのルールに基づいています。

最後に、kube-schedulerは最も高いランクのNodeに対してPodを割り当てます。もし同一のスコアのNodeが複数ある場合は、kube-schedulerがランダムに1つ選択します。

デフォルトのポリシーについて

kube-schedulerは、デフォルトで用意されているスケジューリングポリシーのセットを持っています。

フィルタリング

  • PodFitsHostPorts: Nodeに、Podが要求するポートが利用可能かどうかをチェックします。

  • PodFitsHost: Podがそのホスト名において特定のNodeを指定しているかをチェックします。

  • PodFitsResources: Nodeに、Podが要求するリソース(例: CPUとメモリー)が利用可能かどうかをチェックします。

  • PodMatchNodeSelector: PodのNodeSelectorが、Nodeのラベルにマッチするかどうかをチェックします。

  • NoVolumeZoneConflict: Podが要求するVolumeがNode上で利用可能かを、障害が発生しているゾーンを考慮して評価します。

  • NoDiskConflict: NodeのVolumeがPodの要求を満たし、すでにマウントされているかどうかを評価します。

  • MaxCSIVolumeCount: CSI Volumeをいくつ割り当てるべきか決定し、それが設定された上限を超えるかどうかを評価します。

  • CheckNodeMemoryPressure: もしNodeがメモリーの容量が逼迫している場合、また設定された例外がない場合はそのPodはそのNodeにスケジュールされません。

  • CheckNodePIDPressure: もしNodeのプロセスIDが枯渇しそうになっていた場合や、設定された例外がない場合はそのPodはそのNodeにスケジュールされません。

  • CheckNodeDiskPressure: もしNodeのストレージが逼迫している場合(ファイルシステムの残り容量がほぼない場合)や、設定された例外がない場合はそのPodはそのNodeにスケジュールされません。

  • CheckNodeCondition: Nodeは、ファイルシステムの空き容量が完全になくなった場合、ネットワークが利用不可な場合、kubeletがPodを稼働させる準備をできていない場合などに、その状況を通知できます。Nodeがこの状況下かつ設定された例外がない場合、Podは該当のNodeにスケジュールされません。

  • PodToleratesNodeTaints: PodのTolerationがNodeのTaintを許容できるかチェックします。

  • CheckVolumeBinding: Podが要求するVolumeの要求を満たすか評価します。これはPersistentVolumeClaimがバインドされているかに関わらず適用されます。

スコアリング

  • SelectorSpreadPriority: 同一のService、StatefulSetや、ReplicaSetに属するPodを複数のホストをまたいで稼働させます。

  • InterPodAffinityPriority: weightedPodAffinityTermの要素をイテレートして合計を計算したり、もし一致するPodAffinityTermがNodeに適合している場合は、"重み"を合計値に足したりします。:最も高い合計値を持つNode(複数もあり)が候補となります。

  • LeastRequestedPriority: 要求されたリソースがより低いNodeを優先するものです。言い換えると、Nodeに多くのPodが稼働しているほど、Podが使用するリソースが多くなり、その要求量が低いNodeが選択されます。

  • MostRequestedPriority: 要求されたリソースがより多いNodeを優先するものです。このポリシーは、ワークロードの全体セットを実行するために必要な最小数のNodeに対して、スケジュールされたPodを適合させます。 

  • RequestedToCapacityRatioPriority: デフォルトのリソーススコアリング関数を使用して、requestedToCapacityベースのResourceAllocationPriorityを作成します。

  • BalancedResourceAllocation: バランスのとれたリソース使用量になるようにNodeを選択します。

  • NodePreferAvoidPodsPriority: Nodeのscheduler.alpha.kubernetes.io/preferAvoidPodsというアノテーションに基づいてNodeの優先順位づけをします。この設定により、2つの異なるPodが同じNode上で実行しないことを示唆できます。

  • NodeAffinityPriority: "PreferredDuringSchedulingIgnoredDuringExecution"の値によって示されたNode Affinityのスケジューリング性向に基づいてNodeの優先順位づけを行います。詳細はNodeへのPodの割り当てにて確認できます。

  • TaintTolerationPriority: Node上における許容できないTaintsの数に基づいて、全てのNodeの優先順位リストを準備します。このポリシーでは優先順位リストを考慮してNodeのランクを調整します。

  • ImageLocalityPriority: すでにPodに対するコンテナイメージをローカルにキャッシュしているNodeを優先します。

  • ServiceSpreadingPriority: このポリシーの目的は、特定のServiceに対するバックエンドのPodが、それぞれ異なるNodeで実行されるようにすることです。このポリシーではServiceのバックエンドのPodがすでに実行されていないNode上にスケジュールするように優先します。これによる結果として、Serviceは単体のNode障害に対してより耐障害性が高まります。

  • CalculateAntiAffinityPriorityMap: このポリシーはPodのAnti-Affinityの実装に役立ちます。

  • EqualPriorityMap: 全てのNodeに対して等しい重みを与えます。

次の項目

4 - スケジューラーのパフォーマンスチューニング

FEATURE STATE: Kubernetes 1.14 [beta]

kube-schedulerはKubernetesのデフォルトのスケジューラーです。クラスター内のノード上にPodを割り当てる責務があります。

クラスター内に存在するノードで、Podのスケジューリング要求を満たすものはPodに対して割り当て可能なノードと呼ばれます。スケジューラーはPodに対する割り当て可能なノードをみつけ、それらの割り当て可能なノードにスコアをつけます。その中から最も高いスコアのノードを選択し、Podに割り当てるためのいくつかの関数を実行します。スケジューラーはBindingと呼ばれる処理中において、APIサーバーに対して割り当てが決まったノードの情報を通知します。

このページでは、大規模のKubernetesクラスターにおけるパフォーマンス最適化のためのチューニングについて説明します。

スコア付けするノードの割合

Kubernetes 1.12以前では、Kube-schedulerがクラスター内の全てのノードに対して割り当て可能かをチェックし、実際に割り当て可能なノードのスコア付けをしていました。Kubernetes 1.12では新機能を追加し、ある数の割り当て可能なノードが見つかった時点で、割り当て可能なノードの探索を止めれるようになりました。これにより大規模なクラスターにおけるスケジューラーのパフォーマンスが向上しました。その数はクラスターのサイズの割合(%)として指定されます。この割合はpercentageOfNodesToScoreというオプションの設定項目によって指定可能です。この値の範囲は1から100までです。100より大きい値は100%として扱われます。0を指定したときは、この設定オプションを指定しないものとして扱われます。Kubernetes 1.14では、この値が指定されていないときは、スコア付けするノードの割合をクラスターのサイズに基づいて決定するためのメカニズムがあります。このメカニズムでは100ノードのクラスターに対しては50%の割合とするような線形な式を使用します。5000ノードのクラスターに対しては10%となります。自動で算出される割合の最低値は5%となります。言い換えると、クラスターの規模がどれだけ大きくても、ユーザーがこの値を5未満に設定しない限りスケジューラーは少なくても5%のクラスター内のノードをスコア付けすることになります。

percentageOfNodesToScoreの値を50%に設定する例は下記のとおりです。

apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
algorithmSource:
  provider: DefaultProvider

...

percentageOfNodesToScore: 50
備考: 割り当て可能なノードが50未満のクラスターにおいては、割り当て可能なノードの探索を止めるほどノードが多くないため、スケジューラーは全てのノードをチェックします。

この機能を無効にするためにはpercentageOfNodesToScoreを100に設定してください。

percentageOfNodesToScoreのチューニング

percentageOfNodesToScoreは1から100の間の範囲である必要があり、デフォルト値はクラスターのサイズに基づいて計算されます。また、クラスターのサイズの最小値は50ノードとハードコードされています。これは数百のノードを持つようなクラスターにおいてこの値を50より低い値に変更しても、スケジューラーが検出する割り当て可能なノードの数に大きな影響を与えないことを意味します。このオプションは意図的なものです。その理由としては、小規模のクラスターにおいてパフォーマンスを著しく改善する可能性が低いためです。1000ノードを超える大規模なクラスターでこの値を低く設定すると、パフォーマンスが著しく改善される可能性があります。

この値を設定する際に考慮するべき重要な注意事項として、割り当て可能ノードのチェック対象のノードが少ないと、一部のノードはPodの割り当てのためにスコアリングされなくなります。結果として、高いスコアをつけられる可能性のあるノードがスコアリングフェーズに渡されることがありません。これにより、Podの配置が理想的なものでなくなります。したがって、この値をかなり低い割合に設定すべきではありません。一般的な経験則として、この値を10未満に設定しないことです。スケジューラーのスループットがアプリケーションにとって致命的で、ノードのスコアリングが重要でないときのみ、この値を低く設定するべきです。言いかえると、割り当て可能な限り、Podは任意のノード上で稼働させるのが好ましいです。

クラスターが数百のノードを持つ場合やそれに満たない場合でも、この設定オプションのデフォルト値を低くするのを推奨しません。デフォルト値を低くしてもスケジューラーのパフォーマンスを大幅に改善することはありません。

スケジューラーはどのようにノードを探索するか

このセクションでは、この機能の内部の詳細を理解したい人向けになります。

クラスター内の全てのノードに対して平等にPodの割り当ての可能性を持たせるため、スケジューラーはラウンドロビン方式でノードを探索します。複数のノードの配列になっているイメージです。スケジューラーはその配列の先頭から探索を開始し、percentageOfNodesToScoreによって指定された数のノードを検出するまで、割り当て可能かどうかをチェックしていきます。次のPodでは、スケジューラーは前のPodの割り当て処理でチェックしたところから探索を再開します。

ノードが複数のゾーンに存在するとき、スケジューラーは様々なゾーンのノードを探索して、異なるゾーンのノードが割り当て可能かどうかのチェック対象になるようにします。例えば2つのゾーンに6つのノードがある場合を考えます。

Zone 1: Node 1, Node 2, Node 3, Node 4
Zone 2: Node 5, Node 6

スケジューラーは、下記の順番でノードの割り当て可能性を評価します。

Node 1, Node 5, Node 2, Node 6, Node 3, Node 4

全てのノードのチェックを終えたら、1番目のノードに戻ってチェックをします。