1. TOP
  2. BLOG
  3. TECH ARTICLE:連載 第10回
    アドバンスドPod
    スケジューリング
TECH ARTICLE

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です。

From : https://blog.kubecost.com/blog/kubernetes-taints/

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が同じノードで実行中であればサービスが停止する可能性があります。

From: https://medium.com/@danielaaronw/k8s-pod-anti-affinity-dd2667a20c5f

このような現象を解決するために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の可用性を保障する用途で使用します。

お気軽にお問い合わせください

製品に関する事やご不明な点など、お気軽にご相談ください。
また、ジェニファーソフトでは2週間の無償トライアルを提供しています。

詳しく知りたい方は
導入や費用のご相談は