Pod Affinity と Anti-Affinity
Podは特定のノードや特定の状況下で実行されるよう制約をかけることができます。これには、ノードごとに1つのアプリケーションPodのみを実行したい場合や、複数のPodをノード上でペアリングしたい場合などが含まれます。さらに、ノードアフィニティを使用する場合、Podには優先的または必須の制約を設定できます。
このレッスンでは、checkout-redis Podをノードごとに1つのインスタンスのみ実行するようにし、checkout Podをcheckout-redis Podが存在するノードにのみ1つのインスタンスを実行するように、Pod間のアフィニティとアンチアフィニティに焦点を当てます。これにより、キャッシングPod(checkout-redis)が最高のパフォーマンスを得るためにcheckout Podインスタンスとローカルで実行されるようになります。
まず最初に、checkoutとcheckout-redis Podが実行されていることを確認しましょう:
NAME READY STATUS RESTARTS AGE
checkout-698856df4d-vzkzw 1/1 Running 0 125m
checkout-redis-6cfd7d8787-kxs8r 1/1 Running 0 127m
両方のアプリケーションがクラスター内で1つのPodを実行していることがわかります。次に、それらがどこで実行されているかを確認しましょう:
checkout-698856df4d-vzkzw ip-10-42-11-142.us-west-2.compute.internal
checkout-redis-6cfd7d8787-kxs8r ip-10-42-10-225.us-west-2.compute.internal
上記の結果から、checkout-698856df4d-vzkzw Podはip-10-42-11-142.us-west-2.compute.internalノードで実行され、checkout-redis-6cfd7d8787-kxs8r Podはip-10-42-10-225.us-west-2.compute.internalノードで実行されていることがわかります。
あな たの環境では、最初にPodが同じノード上で実行されている場合があります
checkoutデプロイメントにpodAffinityとpodAntiAffinityポリシーを設定して、ノードごとに1つのcheckout Podが実行され、checkout-redis Podが既に実行されているノードでのみ実行されるようにしましょう。優先的な動作ではなく要件とするためにrequiredDuringSchedulingIgnoredDuringExecutionを使用します。
次のKustomizationはcheckoutデプロイメントにpodAffinityとpodAntiAffinityポリシーの両方を指定したaffinityセクションを追加します:
- Kustomize Patch
- Deployment/checkout
- Diff
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout
namespace: checkout
spec:
template:
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/component
operator: In
values:
- redis
topologyKey: kubernetes.io/hostname
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/component
operator: In
values:
- service
- key: app.kubernetes.io/instance
operator: In
values:
- checkout
topologyKey: kubernetes.io/hostname
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/created-by: eks-workshop
app.kubernetes.io/type: app
name: checkout
namespace: checkout
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/component: service
app.kubernetes.io/instance: checkout
app.kubernetes.io/name: checkout
template:
metadata:
annotations:
prometheus.io/path: /metrics
prometheus.io/port: "8080"
prometheus.io/scrape: "true"
labels:
app.kubernetes.io/component: service
app.kubernetes.io/created-by: eks-workshop
app.kubernetes.io/instance: checkout
app.kubernetes.io/name: checkout
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/component
operator: In
values:
- redis
topologyKey: kubernetes.io/hostname
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/component
operator: In
values:
- service
- key: app.kubernetes.io/instance
operator: In
values:
- checkout
topologyKey: kubernetes.io/hostname
containers:
- envFrom:
- configMapRef:
name: checkout
image: public.ecr.aws/aws-containers/retail-store-sample-checkout:1.2.1
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 3
name: checkout
ports:
- containerPort: 8080
name: http
protocol: TCP
resources:
limits:
memory: 512Mi
requests:
cpu: 250m
memory: 512Mi
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumeMounts:
- mountPath: /tmp
name: tmp-volume
securityContext:
fsGroup: 1000
serviceAccountName: checkout
volumes:
- emptyDir:
medium: Memory
name: tmp-volume
app.kubernetes.io/created-by: eks-workshop
app.kubernetes.io/instance: checkout
app.kubernetes.io/name: checkout
spec:
+ affinity:
+ podAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchExpressions:
+ - key: app.kubernetes.io/component
+ operator: In
+ values:
+ - redis
+ topologyKey: kubernetes.io/hostname
+ podAntiAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchExpressions:
+ - key: app.kubernetes.io/component
+ operator: In
+ values:
+ - service
+ - key: app.kubernetes.io/instance
+ operator: In
+ values:
+ - checkout
+ topologyKey: kubernetes.io/hostname
containers:
- envFrom:
- configMapRef:
name: checkout
上記のマニフェストでは、podAffinityセクションが次のことを保証します:
- CheckoutのPodはRedisのPodが実行されているノードでのみスケジュールされます。
- これは
app.kubernetes.io/component: redisというラベルを持つPodとマッチングすることで実施されます。 topologyKey: kubernetes.io/hostnameはこのルールがノードレベルで適用されることを保証します。
podAntiAffinityセクションは次のことを保証します:
- ノードごとに1つのcheckout Podのみが実行されます。
- これは
app.kubernetes.io/component: serviceとapp.kubernetes.io/instance: checkoutのラベルを持つPodが同じノード上で実行されないようにすることで実現されます。
変更を適用するために、次のコマンドを実行してクラスター内のcheckoutデプロイメントを変更します:
namespace/checkout unchanged
serviceaccount/checkout unchanged
configmap/checkout unchanged
service/checkout unchanged
service/checkout-redis unchanged
deployment.apps/checkout configured
deployment.apps/checkout-redis unchanged
podAffinityセクションは、checkout-redis Podが 既にノード上で実行されていることを保証します - これはcheckout Podが正しく実行されるためにcheckout-redisを必要とすると仮定できるからです。podAntiAffinityセクションは、**app.kubernetes.io/component=service**ラベルと一致するノード上に既にcheckout Podが実行されていないことを要求します。では、デプロイメントをスケールアップして設定が機能しているか確認しましょう:
各Podがどこで実行されているか検証します:
checkout-6c7c9cdf4f-p5p6q ip-10-42-10-120.us-west-2.compute.internal
checkout-6c7c9cdf4f-wwkm4
checkout-redis-6cfd7d8787-gw59j ip-10-42-10-120.us-west-2.compute.internal
この例では、最初のcheckout Podは既存のcheckout-redis Podと同じノード上で実行されており、設定したpodAffinityルールを満たしています。2番目のPodはまだPending状態です。これは、私たちが定義したpodAntiAffinityルールにより、同じノード上で2つのcheckout Podを起動できないためです。2番目のノードにはcheckout-redis Podが実行されていないため、Pendingのままです。
次に、私たちの2つのノード用にcheckout-redisを2つのインスタンスにスケールしますが、最初にcheckout-redisデプロイメントポリシーを変更して、checkout-redisインスタンスが各ノードに分散されるようにしましょう。これを行うには、podAntiAffinityルールを作成するだけです。
- Kustomize Patch
- Deployment/checkout-redis
- Diff
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout-redis
labels:
app.kubernetes.io/created-by: eks-workshop
app.kubernetes.io/team: database
spec:
template:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/component
operator: In
values:
- redis
topologyKey: kubernetes.io/hostname
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/created-by: eks-workshop
app.kubernetes.io/team: database
name: checkout-redis
namespace: checkout
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/component: redis
app.kubernetes.io/instance: checkout
app.kubernetes.io/name: checkout
template:
metadata:
labels:
app.kubernetes.io/component: redis
app.kubernetes.io/created-by: eks-workshop
app.kubernetes.io/instance: checkout
app.kubernetes.io/name: checkout
app.kubernetes.io/team: database
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/component
operator: In
values:
- redis
topologyKey: kubernetes.io/hostname
containers:
- image: public.ecr.aws/docker/library/redis:6.0-alpine
imagePullPolicy: IfNotPresent
name: redis
ports:
- containerPort: 6379
name: redis
protocol: TCP
app.kubernetes.io/instance: checkout
app.kubernetes.io/name: checkout
app.kubernetes.io/team: database
spec:
+ affinity:
+ podAntiAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchExpressions:
+ - key: app.kubernetes.io/component
+ operator: In
+ values:
+ - redis
+ topologyKey: kubernetes.io/hostname
containers:
- image: public.ecr.aws/docker/library/redis:6.0-alpine
imagePullPolicy: IfNotPresent
name: redis
上記のマニフェストでは、podAntiAffinityセクションが次のことを保証します:
- RedisのPodは異なるノードに分散されます。
- これは
app.kubernetes.io/component: redisというラベルを持つ複数のPodが同じノード上で実行されないようにすることで実施されます。 topologyKey: kubernetes.io/hostnameはこのルールがノードレベルで適用されることを保証します。
次のコマンドでそれを適用します:
namespace/checkout unchanged
serviceaccount/checkout unchanged
configmap/checkout unchanged
service/checkout unchanged
service/checkout-redis unchanged
deployment.apps/checkout unchanged
deployment.apps/checkout-redis configured
podAntiAffinityセクションは、**app.kubernetes.io/component=redis**ラベルと一致するノード上に既にcheckout-redis Podが実行されていないことを要求します。
実行中のPodを確認して、それぞれ2つずつ実行されていることを検証します:
NAME READY STATUS RESTARTS AGE
checkout-5b68c8cddf-6ddwn 1/1 Running 0 4m14s
checkout-5b68c8cddf-rd7xf 1/1 Running 0 4m12s
checkout-redis-7979df659-cjfbf 1/1 Running 0 19s
checkout-redis-7979df659-pc6m9 1/1 Running 0 22s
また、Podがどこで実行されているかを確認し、podAffinityとpodAntiAffinityポリシーが守られていることを確認することもできます:
checkout-5b68c8cddf-bn8bp ip-10-42-11-142.us-west-2.compute.internal
checkout-5b68c8cddf-clnps ip-10-42-12-31.us-west-2.compute.internal
checkout-redis-7979df659-57xcb ip-10-42-11-142.us-west-2.compute.internal
checkout-redis-7979df659-r7kkm ip-10-42-12-31.us-west-2.compute.internal
Podのスケジューリングは問題なさそうですが、checkout Podをさらにスケールして、3つ目のPodがどこにデプロイされるかを確認することで、さらに検証できます:
実行中のPodを確認すると、3つ目のcheckout Podは、すでに2つのノードにPodがデプロイされており、3つ目のノードにはcheckout-redis Podが実行されていないため、Pending状態になっていることがわかります。
NAME READY STATUS RESTARTS AGE
checkout-5b68c8cddf-bn8bp 1/1 Running 0 4m59s
checkout-5b68c8cddf-clnps 1/1 Running 0 6m9s
checkout-5b68c8cddf-lb69n 0/1 Pending 0 6s
checkout-redis-7979df659-57xcb 1/1 Running 0 35s
checkout-redis-7979df659-r7kkm 1/1 Running 0 2m10s
Pending状態のPodを削除してこのセクションを終了しましょう: