2024年09月04日
連載 第10回
アドバンスドPod
スケジューリング
今回はPodのスケジューリングをより細かく行う方法を確認します。
主な内容:
• サービスの安定性のためにPod間で実行可能なノードを分離して、同じアプリケーションがPodの異なるノードで実行するアドバンスドPodスケジューリング(Advanced POD Scheduling)機能を確認します。
実習課題:
• Taint、Tolerations、Node Affinityを設定して特定のノードでPodを実行します。
• Pod Anti-Affinityを設定して同じノードでPodが実行できないようにします。
今回の実習で使用するソースファイルのGitHubディレクトリは次の通りです。
• https://github.com/junghoon2/k8s-class/tree/main/affinity-toleration
クバネティスの基本のスケジューリングポリシーはクラスターのリソース活用を最大化し、Podの可用性を保障することに重点を置きます。特別な要求事項が無い場合、クバネティスは基本のスケジューリングポリシーによって、Podを任意の適切なノードに自動で配置します。スケジューリング機能はコントロールプレーンのスケジューラPod(Scheduler Pod)が担当します。スケジューラPodはクラスターで新しいPodをどのノードに配置するかを決定します。
EKSはコントロールプレーンをAWSで管理しており、スケジューラPodを確認できません。しかし、オンプレミスネイティブクバネティス環境ではスケジューラPodを下記のように確認できます。
1. $ (⎈ |alooo:kube-system) k get pod --selector component=kube-scheduler -o wide -n kube-system
2. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
3. kube-scheduler-master1 1/1 Running 61 (12h ago) 126d 192.168.1.101 master1 <none> <none>
4. kube-scheduler-master2 1/1 Running 87 (12h ago) 126d 192.168.1.102 master2 <none> <none>
5. kube-scheduler-master3 1/1 Running 96 (12h ago) 126d 192.168.1.103 master3 <none> <none>
–selectorオプションを利用すると、特定ラベルのPodのみを指定して表示します。PodスケジューラはPodを配置するとき、ノードが割当可能な全体リソース容量(メモリ、CPU容量など)とPodのリソースリクエスト量(request)を考慮して最も適切なノードを選択します。また、ノードの実行可能状態(Healthiness)を確認して、異常があるノードではPodを実行させません。
注意する事は、Podのリソースリクエスト量はPodの実際のリソース使用量ではなく、マニュフェストに明示したPodのリソースリクエスト容量が基準であることです。実際の使用量が高いのにリソースリクエストが低い、反対に使用量が低いのにリソースリクエストは高い場合があります。そのときはノードの使用可能なリソースが不足し、PodがOOM(Out Of Memory)障害を発生することがあります。実際の使用量に基づく適切なRequestの設定が必要です。
クバネティスのアドバンスドPodスケジューリングは、Podをより細くスケジューリングする方法をサポートします。基本的にクバネティスは上記のように、Podをクラスター内のどのノードにも配置できるように自由にスケジューリングします。しかし、状況によってはPodを特定のノードに配置する必要があります。この場合にアドバンスドPodスケジューリングを使用してより細かくスケジューリングを制御できます。
1.Taint、Tolerations、Node Affinityの設定
筆者は、サービスに関係するPodと、実際のサービスとは無関係で共通のインフラ運用に関連するモニタリング、ログ、ArgoCDなどの関係するPodを分離して、異なるノードグループで実行します。何故なら、ログの検索やモニタリング用のPodが、偶にPodのリソースを多く消費する場合があり、実サービスと関連するPodに影響を与えないようにするためです。このように明示的に特定のPodを分離する場合に使用するオプションがtaintとtolerationsです。
Taint(汚れ)の設定は特定のノードに特別な制約事項を与えます。ノードにTaintを設定するとノードにPodをスケジューリングする前に、Taintと一致するTolerations(容認)があるPodのみがノードで実行されます。これにより特定のノードを特別な用途に使用する、又は特定のPodを特定ノードのみにスケジューリングするようにできます。
TaintとTolerationsの差はTaintの設定はノードへの設定で、torelationの設定はPodに設定することです。Taintをノードに設定して、Tolerationsの設定をPodに指定すると、そのノードには他のPodはスケジューリングせず、Tolerationsの設定を持っているPodのみが実行されます。
Podが特定のノードを選択してスケジューリングするためには、nodeSelector(選択)又はnodeAffinityの設定をします。実務でkarpenter Provisionerの設定に下記のようにTaintを設定し、Podを細かくスケジューリングします。karpenter Provisioner ManifestファイルにTaintの設定を追加した例は下記の通りです。
taint-provisioner.yaml
1. apiVersion: karpenter.sh/v1alpha5
2. kind: Provisioner
3. metadata:
4. name: webrtc
5. namespace: karpenter
6. spec:
7. providerRef:
8. name: default
9. taints:
10. - effect: NoSchedule
11. key: node
12. value: webrtc
13. labels:
14. node: webrtc
15. (省略)
. spec.taints
Taintの設定はKey-Valueタイプです。Key-Valueは任意に指定できます。筆者は Key-Valueをnode:webrtcに指定しました。effect: NoScheduleを指定して、Tolerationsがnode:webrtcを持っていないPodはスケジューリングできないようにしました。
次にTaintの設定を持っているノードでPodが実行できるように、Podの設定にTolerationsとnodeAffinityの設定を追加します。TolerationsはTaintの設定を持っているノードに配布できるようにするオプションです。nodeAffinityはPodを特定のノード又は特定のノードグループにスケジューリングするときに使用します。nodeAffinityを使用するとPodを特定のノードにスケジューリングできます。nodeSelectorより柔軟な条件設定ができ、podAffinityと類似した文法のため、nodeSelectorの代わりにnodeAffinityの設定が多く使用されます。Tolerationsの設定だけでnodeAffinityが無ければ、Podが任意のノードにスケジューリングできるため、nodeAffinityの設定をtorelationsの設定と同時に追加します。
nodeAffinityはノードのラベルを基準に、Podのspecフィールドに affinityブロックを追加して定義します。ノード のAffinityは次のような2つのオプションを持つことができます。
. RequiredDuringSchedulingIgnoredDuringExecution
このタイプはPodを必ず(Required)特定のノード又はノードグループにスケジューリングする必要があり、条件を満足できない場合はPodをスケジューリングしません。ただし、Podを既にノードにスケジューリングしていると、ノードのAffinity条件が変更されてもPodは他のノードへ移動されません (Ignored)。
. PreferredDuringSchedulingIgnoredDuringExecution
Podが特定のノード又はノードグループにスケジューリングできることを優先(Preferred)しますが、満足するノードが無い場合、条件を満足できなくてもPodを他のノードにスケジューリングできます。ノードのAffinityが優先される条件ですが強制性はありません。
実習で確認します。
toleration-deploy.yaml
1. apiVersion: apps/v1
2. kind: Deployment
3. metadata:
4. name: nginx-hello
5. namespace: default
6. labels:
7. app: nginx
8. spec:
9. replicas: 2
10. selector:
11. matchLabels:
12. app: nginx
13. template:
14. metadata:
15. labels:
16. app: nginx
17. spec:
18. containers:
19. - name: nginx
20. image: nginxdemos/hello
21. tolerations:
22. - key: "node"
23. value: "webrtc"
24. effect: "NoSchedule"
25. affinity:
26. nodeAffinity:
27. requiredDuringSchedulingIgnoredDuringExecution:
28. nodeSelectorTerms:
29. - matchExpressions:
30. - key: node
31. operator: In
32. values:
33. - webrtc
. tolerations
Taint設定されたノードにスケジューリングできるようにTolerationsの設定を追加します。 node:webrtc設定でTaint設定を持っているノードに配布できます。
. nodeAffinity
ラベルがnode:webrtcを持っているノードのみPodを配布できるようにnodeAffinityの設定をします。Podが他のノードではない特定のノードのみにスケジューリングできるように、nodeAffinityの requiredDuringSchedulingIgnoredDuringExecutionの設定を追加しました。
では、provisionerと Podを実行します。
1. $ (⎈ |switch-singapore-test:karpenter) k apply -f taint-provisioner.yaml
2. provisioner.karpenter.sh/webrtc created
3.
4. $ (⎈ |switch-singapore-test:default) k apply -f tolerations-deploy.yml
5. deployment.apps/nginx-hello created
最初に、nginx-hello Podの状態はpendingです。何故でしょうか?
1. $ (⎈ |switch-singapore-test:default) k get pod -o wide
2. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
3. nginx-hello-558fcbd7bb-rffrz 0/1 Pending 0 3s <none> <none> <none> <none>
4. nginx-hello-558fcbd7bb-x9xdt 0/1 Pending 0 3s <none> <none> <none> <none>
nodeAffinityの node:webrtc Labelを持っているノードがないからです。karpenterがまだ新しいノードを配布する前です。少し待つと、karpenter Provisionerが node:webrtc Labelを持っているノードを配布します。
1. $ (⎈ |switch-singapore-test:default) k get pod -o wide
2. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
3. nginx-hello-558fcbd7bb-rffrz 1/1 Running 0 17m 10.110.14.130 ip-10-110-11-102.ap-southeast-1.compute.internal <none> <none>
4. nginx-hello-558fcbd7bb-x9xdt 1/1 Running 0 17m 10.110.2.0 ip-10-110-11-102.ap-southeast-1.compute.internal <none> <none>
次に、nginx-hello Podが実行されます。2つのPodが同じip-10-110-11-102-*というホスト名を持っているノードで実行されました。このノードだけがnode:webrtc Labelを含むからです。
次は任意のPodを実行してTaint設定されたノードにPodがスケジューリングできるかを確認します。
1. $ (⎈ |switch-singapore-test:default) k create deployment nginx --image nginx --replicas 10
2. deployment.apps/nginx created
Nginxイメージを持っている Nginx Pod を10個実行しました。
1. $ (⎈ |switch-singapore-test:default) k get pod -o wide
2. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
3. nginx-748c667d99-26q6g 1/1 Running 0 51s 10.110.22.225 ip-10-110-24-222.ap-southeast-1.compute.internal <none> <none>
4. nginx-748c667d99-6b977 1/1 Running 0 51s 10.110.28.221 ip-10-110-24-222.ap-southeast-1.compute.internal <none> <none>
5. nginx-748c667d99-85bbt 1/1 Running 0 51s 10.110.19.155 ip-10-110-24-222.ap-southeast-1.compute.internal <none> <none>
6. nginx-748c667d99-8679g 1/1 Running 0 51s 10.110.20.120 ip-10-110-24-222.ap-southeast-1.compute.internal <none> <none>
7. nginx-748c667d99-8hnhv 1/1 Running 0 51s 10.110.42.118 ip-10-110-47-109.ap-southeast-1.compute.internal <none> <none>
8. nginx-748c667d99-cg4w2 1/1 Running 0 51s 10.110.22.21 ip-10-110-24-222.ap-southeast-1.compute.internal <none> <none>
9. nginx-748c667d99-k99b5 1/1 Running 0 51s 10.110.44.197 ip-10-110-47-109.ap-southeast-1.compute.internal <none> <none>
10. nginx-748c667d99-pptxm 1/1 Running 0 51s 10.110.37.91 ip-10-110-47-109.ap-southeast-1.compute.internal <none> <none>
11. nginx-748c667d99-rnk2s 1/1 Running 0 51s 10.110.39.167 ip-10-110-47-109.ap-southeast-1.compute.internal <none> <none>
12. nginx-748c667d99-ttmhp 1/1 Running 0 51s 10.110.45.96 ip-10-110-47-109.ap-southeast-1.compute.internal <none> <none>
13. nginx-hello-558fcbd7bb-rffrz 1/1 Running 0 19m 10.110.14.130 ip-10-110-11-102.ap-southeast-1.compute.internal <none> <none>
14. nginx-hello-558fcbd7bb-x9xdt 1/1 Running 0 19m 10.110.2.0 ip-10-110-11-102.ap-southeast-1.compute.internal <none> <none>
10個のPodを実行しましたが、Podは全てip-10-110-11-102-以外のノードで実行されました。 Nginx PodはTolerations設定が無いため、Taint設定されているip-10-110-11-102-のノードに配布できないからです。
このようにTaint、Tolerations、Node Affinityの設定で特定のノードグループで必要なPodのみが実行できます。GPUノードを使用するとか、メモリ又はCPUを多く使用するPodは特定のインスタンスタイプ(R又はC Type)に配布して、ノードコストを削減する用途で使用できます。
実習を終了した後、PodとProvisionerリソースを削除します。
1. $ (⎈ |switch-singapore-test:default) k delete deployments.apps nginx nginx-hello
2. deployment.apps "nginx" deleted
3. deployment.apps "nginx-hello" deleted
4.
5. $ (⎈ |switch-singapore-test:default) k delete provisioners.karpenter.sh webrtc
6. provisioner.karpenter.sh "webrtc" deleted
2.Pod Anti-Affinityの設定
同じアプリケーションが複数のPodで実行する場合、特定のノードでアプリケーションPodが重複して実行すると、ノードに障害が発生した場合に、同じサービスの複数のPodが終了するため障害が発生することがあります。ノード障害と関係なくkarpenterがコスト削減の理由で、特定のノードを統合(Consolidation)している場合、アプリケーションの全てのPodが同じノードで実行中であればサービスが停止する可能性があります。
このような現象を解決するためにPod Anti-Affinityの設定をします。 Pod Anti-Affinityの設定は特定のラベルを持つPodが、同じノードに同時にスケジューリングされることを防ぎます。つまり、PodアンチAffinityを使用すると、特定のノードに同時に複数のPodが配置できないようにPodを分散します。これによりサービスの可用性と安定性を向上させることができます。Affinityが「親近感」などの意味を持っていますが、Anti-Affinityは「親近感が無い」と理解すると分かり易いです。
PodアンチAffinityを使用する主な理由は次の通りです。
• 高可用性の保障:異なるノードにPodを分散して配置するため、ノード又はロックレベルの障害でもPodが継続的に可用性を維持できます。
• 性能バランスの調整:同じノードに複数のPodを配置すると、ノードのリソースを均等に使用することが難しいです。Pod Anti -Affinityでリソース負荷をバランスよく分散します。
• Podの安定性の確保:異なるPodを同じノードにスケジューリングすると、リソースを多く使用する特定のPod同士がお互いのリソースの使用に影響を与える可能性があります。アンチAffinityを使用することで、それぞれのPodが独立して動作できるように保障されます。
Pod Anti -Affinityは主にMySQL、MariaDB、Redisなどのデータベース関連のPodに必須で使用します。HELMで設置するとデフォルト設定にPodアンチAffinity設定が含まれます。
実習で詳細を確認します。Apache Webサーバ (httpd)を配布します。
pod-anti-affinity-deploy.yaml
1. apiVersion: apps/v1
2. kind: Deployment
3. metadata:
4. name: httpd
5. labels:
6. app: httpd
7. spec:
8. replicas: 2
9. selector:
10. matchLabels:
11. app: httpd
12. template:
13. metadata:
14. labels:
15. app: httpd
16. spec:
17. containers:
18. - name: httpd
19. image: httpd
20. affinity:
21. podAntiAffinity:
22. requiredDuringSchedulingIgnoredDuringExecution:
23. - labelSelector:
24. matchExpressions:
25. - key: app
26. operator: In
27. values:
28. - httpd
29. topologyKey: kubernetes.io/hostname
. affinity
以前のnodeAffinityと同じくaffinityフィールドに podAntiAffinityオプションを追加します。
. podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution
同じラベルを持っているPodは常に同じノードで実行できないようにします。
. labelSelector
Affinityを適用するPodをラベル基準で選択します。多様なリソースにラベルが使用されています。
. topologyKey: kubernetes.io/hostname
Podが実行されるノードのホスト名を基準に区分します。Topology情報はクラスター内のノードの物理的な配置とネットワーク接続性を表すことに使用されます。Topologyを AWSの zoneに指定すると異なるゾーン(Availability Zone)で実行するように指定できます。
Affinity属性を持っている Podを配布します。
1. $ (⎈ |switch-singapore-test:default) k apply -f affinity-deploy.yml
2. deployment.apps/httpd created
意図した通り異なる2つのノードに配布されます。
1. $ (⎈ |switch-singapore-test:default) k get pod -o wide
2. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
3. httpd-77d4584879-6l5bp 1/1 Running 0 20s 10.110.37.91 ip-10-110-47-109.ap-southeast-1.compute.internal <none> <none>
4. httpd-77d4584879-r4l29 1/1 Running 0 20s 10.110.28.221 ip-10-110-24-222.ap-southeast-1.compute.internal <none> <none>
では、1つのPodを追加します。どのようになるでしょうか?
現在のノードは2つであるため、3つの Podを実行することはできません。Podは同じノードで実行できないため、Anti-Affinity設定になっています。
1. $ (⎈ |switch-singapore-test:default) k scale deployment httpd --replicas 3
2. deployment.apps/httpd scaled
1. $ (⎈ |switch-singapore-test:default) k get pod -o wide
2. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
3. httpd-77d4584879-6l5bp 1/1 Running 0 2m13s 10.110.37.91 ip-10-110-47-109.ap-southeast-1.compute.internal <none> <none>
4. httpd-77d4584879-r4l29 1/1 Running 0 2m13s 10.110.28.221 ip-10-110-24-222.ap-southeast-1.compute.internal <none> <none>
5. httpd-77d4584879-rbchf 0/1 Pending 0 13s <none> <none> <none> <none>
最後のPodの状態は Pendingです。詳細メッセージを確認します。
1. $ (⎈ |switch-singapore-test:default) k describe pod httpd-77d4584879-rbchf
2. (省略)
3. Events:
4. Type Reason Age From Message
5. ---- ------ ---- ---- -------
6. Warning FailedScheduling 35s default-scheduler 0/2 nodes are available: 2 node(s) didn't match pod anti-affinity rules. preemption: 0/2 nodes are available: 2 No preemption victims found for incoming pod..
実行中のノードは podAntiAffinity設定で、Podが実行するのに適合していないメッセージが表示されました。PodがPending状態であれば、karpenterがPodの条件を確認し、新しいノードを実行します。少し待つとkarpenterが新しいノードを実行します。
1. $ (⎈ |switch-singapore-test:default) k get pod -o wide
2. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
3. httpd-77d4584879-6l5bp 1/1 Running 0 8m27s 10.110.37.91 ip-10-110-47-109.ap-southeast-1.compute.internal <none> <none>
4. httpd-77d4584879-r4l29 1/1 Running 0 8m27s 10.110.28.221 ip-10-110-24-222.ap-southeast-1.compute.internal <none> <none>
5. httpd-77d4584879-rbchf 1/1 Running 0 6m27s 10.110.47.139 ip-10-110-47-47.ap-southeast-1.compute.internal <none> <none>
次は3つのPodが異なるノードで実行されました。
eks-node-viewerで確認すると、最後に実行したノードは t3.microで、コストが最も安いノードでした。実行されるhttpd Podのリソースリクエスト量(request)の設定が無いため、最もリクエスト量が少ないノードで実行できるからです。
参考までにtopologySpreadConstraints設定を使用すると、異なるゾーン(AZ)に配置したノードにPodが配布されます。ノードのみならずAZの可用性の保障が必要ならば、異なるAZに配置されたノードに配布される必要があります。下記の設定を使用します。
1. topologySpreadConstraints:
2. - maxSkew: 1
3. topologyKey: topology.kubernetes.io/zone
4. whenUnsatisfiable: DoNotSchedule
5. labelSelector:
6. matchLabels:
7. app: dive-backend-admin-api-prod
カスタムしたHELMチャートでアプリケーションを配布する場合、Pod Anti-Affinityの代わりにtopologySpreadConstraints設定を使用すると、同じく異なるAZに配布したノードにPodを配布できます。
最後に実習が終了したためhttpdデプロイメントを削除します。
1. $ (⎈ |switch-singapore-test:default) k delete deployments.apps httpd
2. deployment.apps "httpd" deleted
以上で今回のPodのスケジューリングをより細かくできるTaint、Tolerations、Node/Pod Affinity設定を実習で確認しました。
1. 詳細は kubecostの回の実習で確認します。
2. topologySpreadConstraints設定を使用して、異なるゾーン(AZ)にPodを配布できるようにすることもできます。ノードのみならずAZの可用性を保障する用途で使用します。