1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PodAffinity と Topology Spread Constraints を併用すると、ローリングアップデートが止まることがあるので注意!

1
Last updated at Posted at 2026-10-04

PodAffinity と Topology Spread Constraints(以降、TSC)を組み合わせると、通常時は問題なく動いている Deployment のローリングアップデートが Pending になることがあります。

この記事では、その再現条件と原因を確認したうえで、matchLabelKeys: [pod-template-hash] を使って解決する方法を紹介します。

今回の PodAffinity は、TSC と競合する状況を意図的に作るために厳しく設定しています。常にこのような設定が必要という意味ではありません。

検証環境

3 台のノードで検証します。

$ kubectl get nodes
NAME          STATUS   ROLES           AGE   VERSION
ip-10-0-0-4   Ready    control-plane   23m   v1.36.5
ip-10-0-0-5   Ready    <none>          16m   v1.36.5
ip-10-0-0-6   Ready    <none>          20m   v1.36.5

以降の図では、これらをそれぞれ Node 1、Node 2、Node 3 と表記します。

deployment-a と deployment-b を配置する

まず、2 レプリカの deployment-b をデプロイします。kubernetes.io/hostname をトポロジードメインとして、同じ Deployment の Pod ができるだけ別ノードに配置されるよう TSC を設定します。

deployment-b.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-b
spec:
  replicas: 2
  selector:
    matchLabels:
      app: deployment-b
  template:
    metadata:
      labels:
        app: deployment-b
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: deployment-b
      containers:
        - name: nginx
          image: nginx:1.29

続いて、deployment-a をデプロイします。こちらにも同じ TSC を設定し、さらに deployment-b の Pod があるノードにしか配置できない PodAffinity を設定します。

deployment-a.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-a
spec:
  replicas: 2
  selector:
    matchLabels:
      app: deployment-a
  template:
    metadata:
      labels:
        app: deployment-a
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: deployment-a
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: deployment-b
              topologyKey: kubernetes.io/hostname
      containers:
        - name: nginx
          image: nginx:1.29

適用します。

$ kubectl apply -f deployment-b.yaml
deployment.apps/deployment-b created

$ kubectl apply -f deployment-a.yaml
deployment.apps/deployment-a created

deployment-b は Node 2 と Node 3 に 1 Pod ずつ配置されます。deployment-a は PodAffinity により、その Pod がある同じ 2 ノードに配置されます。

$ kubectl get pods -o wide
NAME                            READY   STATUS    RESTARTS   AGE   IP           NODE
deployment-a-84d676876d-fb7h4   1/1     Running   0          20s   10.42.1.15   ip-10-0-0-5
deployment-a-84d676876d-rrt8r   1/1     Running   0          20s   10.42.2.14   ip-10-0-0-6
deployment-b-5f77d6ff9f-bfdw8   1/1     Running   0          31s   10.42.1.14   ip-10-0-0-5
deployment-b-5f77d6ff9f-hgq7s   1/1     Running   0          31s   10.42.2.13   ip-10-0-0-6

01-initial-deployments.png

ローリングアップデートで新しい Pod が Pending になる

この状態で deployment-a をローリングアップデートします。ここでは挙動を確認するため、rollout restart を実行します。

$ kubectl rollout restart deployment/deployment-a
deployment.apps/deployment-a restarted

新しい ReplicaSet の Pod が 1 つ作成されますが、Pending のままになります。

$ kubectl get pods -o wide
NAME                             READY   STATUS    RESTARTS   AGE   IP           NODE
deployment-a-68b887d5f6-d5qqv    0/1     Pending   0          12s   <none>       <none>
deployment-a-84d676876d-fb7h4    1/1     Running   0          11m   10.42.1.15   ip-10-0-0-5
deployment-a-84d676876d-rrt8r    1/1     Running   0          11m   10.42.2.14   ip-10-0-0-6
deployment-b-5f77d6ff9f-bfdw8    1/1     Running   0          12m   10.42.1.14   ip-10-0-0-5
deployment-b-5f77d6ff9f-hgq7s    1/1     Running   0          12m   10.42.2.13   ip-10-0-0-6

イベントを見ると、Node 1 は PodAffinity を満たさず、Node 2 と Node 3 は TSC を満たさないため、どのノードにも配置できないことが分かります。

Warning  FailedScheduling  default-scheduler  0/3 nodes are available: 1 node(s) didn't match pod affinity rules, 2 node(s) didn't match pod topology spread constraints.

なぜ TSC を満たさないのでしょうか。

deployment-a の TSC の labelSelector は app: deployment-a です。そのため、更新前の Pod と更新後の Pod を区別せず、どちらも偏りの計算対象になります。

新しい Pod を Node 2 または Node 3 に配置すると、deployment-a の分布は 0 / 2 / 1 になります。最小の 0 と最大の 2 の差は maxSkew: 1 を超えるため、whenUnsatisfiable: DoNotSchedule によってスケジュールできません。

一方、Node 1 には deployment-b の Pod がないため、必須の PodAffinity を満たせません。結果として、新しい Pod は Pending のままになり、ローリングアップデートが進みません。

02-rolling-update-pending.png

matchLabelKeys で更新前後の Pod を分けて数える

このケースでは、deployment-a.yaml のマニフェストで、TSC の matchLabelKeys に pod-template-hash を指定します。

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: kubernetes.io/hostname
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: deployment-a
    matchLabelKeys:        # 追加
      - pod-template-hash  # 追加

pod-template-hash は Deployment が ReplicaSet ごとに Pod へ付与するラベルです。matchLabelKeys に指定すると、スケジューリング対象の Pod と同じ値を持つ Pod だけが TSC の偏り計算に含まれます。

つまり、更新中の新しい Pod は更新前の Pod と別のグループとして扱われます。最初の新しい Pod を Node 2 に配置する場合、新しい ReplicaSet だけで見た分布は 0 / 1 / 0 です。差は 1 なので maxSkew: 1 を満たし、配置できます。

この設定を含むマニフェストを適用します。

$ kubectl apply -f deployment-a.yaml
deployment.apps/deployment-a configured

その後、もう一度ローリングアップデートします。

$ kubectl rollout restart deployment/deployment-a
deployment.apps/deployment-a restarted

$ kubectl rollout status deployment/deployment-a
deployment "deployment-a" successfully rolled out

更新後の Pod は Node 2 と Node 3 に 1 Pod ずつ配置され、更新は完了します。

$ kubectl get pods -o wide
NAME                             READY   STATUS    RESTARTS   AGE   IP           NODE
deployment-a-6dcdfdc889-kzbrx    1/1     Running   0          26s   10.42.1.18   ip-10-0-0-5
deployment-a-6dcdfdc889-xz62p    1/1     Running   0          22s   10.42.2.17   ip-10-0-0-6
deployment-b-5f77d6ff9f-bfdw8    1/1     Running   0          26m   10.42.1.14   ip-10-0-0-5
deployment-b-5f77d6ff9f-hgq7s    1/1     Running   0          26m   10.42.2.13   ip-10-0-0-6

以下の図の上段は更新途中、下段は更新完了後の状態です。更新途中では、旧 ReplicaSet と新 ReplicaSet がそれぞれの pod-template-hash ごとに TSC を満たします。そのため、新しい Pod を起動してから古い Pod を終了するローリングアップデートを進められます。

03-match-label-keys-resolved.png

まとめ

PodAffinity と whenUnsatisfiable: DoNotSchedule の TSC を併用すると、更新前後の Pod をまとめて数えることで、ローリングアップデート時だけスケジューリング不能になる場合があります。

Deployment の更新前後で Pod を区別して TSC を評価したい場合は、matchLabelKeys: [pod-template-hash] を指定できます。これにより、更新中の新しい Pod も TSC を満たして配置でき、ローリング更新を進められます。

参考

1
0
0

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?