LoginSignup
0

More than 3 years have passed since last update.

一足遅れて Kubernetes を学び始める - 14. スケジューリング -

Last updated at Posted at 2019-06-05

ストーリー

  1. 一足遅れて Kubernetes を学び始める - 01. 環境選択編 -
  2. 一足遅れて Kubernetes を学び始める - 02. Docker For Mac -
  3. 一足遅れて Kubernetes を学び始める - 03. Raspberry Pi -
  4. 一足遅れて Kubernetes を学び始める - 04. kubectl -
  5. 一足遅れて Kubernetes を学び始める - 05. workloads その1 -
  6. 一足遅れて Kubernetes を学び始める - 06. workloads その2 -
  7. 一足遅れて Kubernetes を学び始める - 07. workloads その3 -
  8. 一足遅れて Kubernetes を学び始める - 08. discovery&LB その1 -
  9. 一足遅れて Kubernetes を学び始める - 09. discovery&LB その2 -
  10. 一足遅れて Kubernetes を学び始める - 10. config&storage その1 -
  11. 一足遅れて Kubernetes を学び始める - 11. config&storage その2 -
  12. 一足遅れて Kubernetes を学び始める - 12. リソース制限 -
  13. 一足遅れて Kubernetes を学び始める - 13. ヘルスチェックとコンテナライフサイクル -
  14. 一足遅れて Kubernetes を学び始める - 14. スケジューリング -
  15. 一足遅れて Kubernetes を学び始める - 15. セキュリティ -
  16. 一足遅れて Kubernetes を学び始める - 16. コンポーネント -

前回

一足遅れて Kubernetes を学び始める - 13. ヘルスチェックとコンテナライフサイクル -では、requestsやlimitといったヘルスチェックの仕方を学びました。今回は、Affinityなどによるスケジューリングについて学習します。

スケジューリング

これから学ぶスケジューリングでは、大きく分けて2つに分類します。

  • Podのスケジューリング時に特定のNodeを選択する方法
    • Affinity
    • Anti-Affinity
  • Nodeに対して汚れをつけて、それを許容できるPodのみスケジューリングを許可する方法
    • 汚れ = Taints
    • 許容 = Tolerations

Nodeのラベル確認

デフォルトで設定されているNodeのラベルを見てみます。

pi@raspi001:~/tmp $ k get nodes -o json | jq ".items[] | .metadata.labels"
{
  "beta.kubernetes.io/arch": "arm",
  "beta.kubernetes.io/os": "linux",
  "kubernetes.io/arch": "arm",
  "kubernetes.io/hostname": "raspi001",
  "kubernetes.io/os": "linux",
  "node-role.kubernetes.io/master": ""
}
{
  "beta.kubernetes.io/arch": "arm",
  "beta.kubernetes.io/os": "linux",
  "kubernetes.io/arch": "arm",
  "kubernetes.io/hostname": "raspi002",
  "kubernetes.io/os": "linux",
  "node-role.kubernetes.io/worker": "worker"
}
{
  "beta.kubernetes.io/arch": "arm",
  "beta.kubernetes.io/os": "linux",
  "kubernetes.io/arch": "arm",
  "kubernetes.io/hostname": "raspi003",
  "kubernetes.io/os": "linux",
  "node-role.kubernetes.io/worker": "worker"
}

archやosはデフォルトで設定されているみたいです。
次以降の学習のため、ラベルをはります。

pi@raspi001:~/tmp $ k label node raspi002 cputype=low disksize=200
pi@raspi001:~/tmp $ k label node raspi003 cputype=low disksize=300

NodeSelector

最も簡単なNodeAffinityの設定です。
指定するラベルに属するNodeにPodを割り当てるようスケジューリングします。簡易なので、equality-baseのみしか指定できません。

では、disksizeが300のNode(raspi003)にPodを配置しましょう。

sample-nodeselector.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sample-nodeselector
  labels:
    app: sample-app
spec:
  containers:
  - name: nginx-container
    image: nginx:1.12
  nodeSelector:
    disksize: "300"
pi@raspi001:~/tmp $ k apply -f sample-nodeselector.yaml
pi@raspi001:~/tmp $ k get pods sample-nodeselector -o wide
NAME                  READY   STATUS    RESTARTS   AGE   IP             NODE       NOMINATED NODE   READINESS GATES
sample-nodeselector   1/1     Running   0          21s   10.244.2.130   raspi003   <none>           <none>

期待通りですね。OKです。
nodeSelectorはイコールでしか表現できないので、柔軟性に欠けます。

Affinity

Affinityは、NodeSelectorよりも柔軟に設定できます。つまり、set-basedの指定方法です。
詳しくはこちらを参照下さい。今回はInオペレータを使います。

sample-node-affinity.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sample-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disktype
            operator: In
            values:
            - hdd
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: kubernetes.io/hostname
            operator: In
            values:
            - raspi002
  containers:
  - name: nginx-container
    image: nginx:1.12

NodeAffinityでは、requiredとpreferredの2つ設定できます。

  • required
    • 必須スケジューリングポリシー
  • preferred
    • 優先的に考慮されるスケジューリングポリシー

必須条件が「cputype=lowであるNode(raspi002,raspi003)」で、優先条件が「hostname=raspi002であるNode」です。
適用してみましょう。

pi@raspi001:~/tmp $ k apply -f sample-node-affinity.yaml
pi@raspi001:~/tmp $ k get pods sample-node-affinity -o wide
NAME                   READY   STATUS              RESTARTS   AGE   IP       NODE       NOMINATED NODE   READINESS GATES
sample-node-affinity   0/1     ContainerCreating   0          5s    <none>   raspi002   <none>           <none>

確かにraspi002に配置されました。では、raspi002をスケジューリングできなくするとどうなるのでしょうか。

pi@raspi001:~/tmp $ k delete -f sample-node-affinity.yaml
pi@raspi001:~/tmp $ k cordon raspi002
pi@raspi001:~/tmp $ k apply -f sample-node-affinity.yaml
pi@raspi001:~/tmp $ k get pods sample-node-affinity -o wide
NAME                   READY   STATUS              RESTARTS   AGE   IP       NODE       NOMINATED NODE   READINESS GATES
sample-node-affinity   0/1     ContainerCreating   0          11s   <none>   raspi003   <none>           <none>

今度は、raspi002をcordonしたので、raspi003に移りました。優先なので、満たされなくても良いのですね。必須条件が満たされなかったら、Pendingになります。

元に戻します。

pi@raspi001:~/tmp $ k delete -f sample-node-affinity.yaml
pi@raspi001:~/tmp $ k uncordon raspi002

ANDとOR

nodeSelectorTermsやmatchExpressionsは配列なので複数指定できます。

sample.yaml
nodeSelectorTerms:
  - matchExpressions:
    - A
    - B
  - matchExpressions:
    - C
    - D

上記の場合は、 (A and B) OR (C and D)という条件になります。

Anti-Affinity

Anti-Affinityは、Affinityの逆です。つまり、特定Node以外のNodeに割り当てるよう
スケジューリングします。特別な指定はなく、単にAffinityの否定形式にするだけです。言葉だけですね。

Inter-Pod Affinity

特定のPodが実行されているドメイン上へPodをスケジューリングするポリシーです。
Pod間を近づけることができるので、レイテンシを下げることができます。

まず、特定のPodは、先程のNodeSelectorで使ったものとします。

pi@raspi001:~/tmp $ k apply -f sample-node-affinity.yaml
pi@raspi001:~/tmp $ k get pods sample-nodeselector -o wide
NAME                  READY   STATUS    RESTARTS   AGE   IP             NODE       NOMINATED NODE   READINESS GATES
sample-nodeselector   1/1     Running   0          36m   10.244.2.130   raspi003   <none>           <none>
sample-pod-affinity-host.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sample-pod-affinity-host
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - sample-app
        topologyKey: kubernetes.io/hostname
  containers:
  - name: nginx-container
    image: nginx:1.12

これだと、sample-appがあるNodeでkubernetes.io/hostname(=raspi003)と同じNodeにPodを割り振ります。つまり、raspi003にできるはずです。

pi@raspi001:~/tmp $ k apply -f sample-pod-affinity-host.yaml
pi@raspi001:~/tmp $  k get pods sample-pod-affinity-host -o wide
NAME                       READY   STATUS              RESTARTS   AGE   IP       NODE       NOMINATED NODE   READINESS GATES
sample-pod-affinity-host   0/1     ContainerCreating   0          11s   <none>   raspi003   <none>           <none>

期待通りraspi003にできています。
また、requiredだけでなく、preferredも設定できます。

sample-pod-affinity-arch.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sample-pod-affinity-arch
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - sample-app
          topologyKey: kubernetes.io/arch
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 1
          podAffinityTerm:
            labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - sample-app
            topologyKey: kubernetes.io/hostname
  containers:
    - name: nginx-container
      image: nginx:1.12

必須条件としては、下記のとおりです。

  • 「labelがapp=sample-appであるPodが動いているNode(raspi003)で、kubernetes.io/archが同じNode(arm)」

これはraspi002(arm),raspi003(arm)どちらにも当てはまります。
そして、優先条件として、下記のとおりです。

  • 「labelがapp=sample-appであるPodが動いているNode(raspi003)で、kubernetes.io/hostnameが同じNode(raspi003)」

これにより、raspi003が選ばれるはずです。

pi@raspi001:~/tmp $ k apply -f sample-pod-affinity-arch.yaml
pi@raspi001:~/tmp $ k get pods sample-pod-affinity-arch -o wide
NAME                       READY   STATUS              RESTARTS   AGE   IP       NODE       NOMINATED NODE   READINESS GATES
sample-pod-affinity-arch   0/1     ContainerCreating   0          13s   <none>   raspi003   <none>           <none>

期待通りraspi003で動いていますね。

Inter-Pod Anti-Affinity

Inter-Pod Affinityの否定形です。以上。

今まで紹介したAffinity、AntiAffinity,Inter-Pod Affinity, Inter-Pod AntiAffinityは、組み合わせることができます。

Taints

Nodeに対して汚れをつけていきます。汚れたNodeに対して、許容するPodのみがスケジューリングできるようになります。

汚れの種類(Effect)は、3つあります。

  • PreferNoSchedule
    • 可能な限りスケジューリングしない
  • NoSchedule
    • スケジューリングしない(既にスケジューリングされているPodはそのまま)
  • NoExecute
    • 実行を許可しない(既にスケジューリングされているPodは停止される)

それでは、まずNodeを汚しましょう。

pi@raspi001:~/tmp $ k taint node raspi003 env=prd:NoSchedule
pi@raspi001:~/tmp $ k describe node raspi003 | grep Taints
Taints:             env=prd:NoSchedule

これでraspi003にPodをスケジューリングできなくなりました。

Tolerations

さきほど汚したNodeに対して、許容(Tolerations)できるPodを作成しましょう。

keyとvalue(env=prd)とEffect(NoSchedule)が設定されたPodのみ許容されます。作ってみます。

sample-tolerations.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sample-tolerations
spec:
  containers:
    - name: nginx-container
      image: nginx:1.12
  tolerations:
  - key: "env"
    operator: "Equal"
    value: "prd"
    effect: "NoSchedule"
  nodeSelector:
    disksize: "300"

※ nodeSelectorで汚れたNodeであるraspi003を指定するようにしています。

operatorには、2種類あります。

  • Equal
    • keyとvalueが等しい
  • Exists
    • keyが存在する

では、適用してみます。

pi@raspi001:~/tmp $ k apply -f sample-tolerations.yaml
pi@raspi001:~/tmp $ k get pod sample-tolerations -o=wide
NAME                 READY   STATUS    RESTARTS   AGE   IP             NODE       NOMINATED NODE   READINESS GATES
sample-tolerations   1/1     Running   0          27s   10.244.2.140   raspi003   <none>           <none>

汚れたNodeに許容されるPodが適用されましたね。
許容を一部変えてみる(env=stg)と、もちろんPendingになりました。

もとに戻しておきます。

pi@raspi001:~/tmp $ k taint node raspi003 env-

お片付け

pi@raspi001:~/tmp $ k delete -f sample-nodeselector.yaml -f sample-node-affinity.yaml -f sample-pod-affinity-host.yaml -f sample-pod-affinity-arch.yaml -f sample-tolerations.yaml
pi@raspi001:~/tmp $ k label node raspi002 cputype- disksize-
pi@raspi001:~/tmp $ k label node raspi003 cputype- disksize-

最後に

PodをどのNodeにスケジューリングするのか学習しました。
汚れ(taint)と許容(tolerations)という考えは面白いなと思いました。
ただ、あまり使いすぎると複雑に陥りやすそうなので、注意が必要ですね。
次回は、こちらです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0