AWS は堅牢であるものの、AZレベルの障害が発生しているため、複数のAZに分散して可用性を高めておきたいものです。
下記の図のように、各ゾーンごとにノードが配置されている場合、Podについてもそれぞれのゾーン毎に配置したいのですが、単純に replicas
の数を増やしても、各ノードのリソースの空き状況に応じて配置されるだけなので、思ったように分散されないこともしばしばです。
PodAffinity / PodAntiAffinity について
Pod をどのノードに配置するかを指定する方法として、 ノードに付与されたラベルから配置先を指定する nodeSelector
と、より柔軟な指定ができる nodeAffinity / nodeAntiAffinity
があります。
これらはあくまでもノードに付与されたラベルから配置先を指定しますが、PodAffinity / PodAntiAffinity
はノードのラベルではなく、すでに配置されたPodのラベルから配置先を指定することができます。
下記の図のように既に実行中のPodを避けて配置先を指定したい場合は PodAntiAffinity
を指定します。
また、指定できる条件として、必須条件の requiredDuringSchedulingIgnoredDuringExecution
と、条件に満たなかった場合でも配置を試みる preferredDuringSchedulingIgnoredDuringExecution
があります。
1AZで複数のノードを使用していたり、リソースの使用状況に応じてノード数やPod数を増減させるような、cluster autoscaler や Horizontal Pod Autoscaler (HPA) を使っている場合、必須条件である requiredDuringSchedulingIgnoredDuringExecution
で指定していると本来必要なPodが実行できないことになるため、preferredDuringSchedulingIgnoredDuringExecution
を指定することになると思います。
動作確認
検証環境として以下のような2AZに1ノードずつ配置したEKSを構築しました。
以下のようなテスト用 Deployment を replicas: 1
で実行します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: podinfo
spec:
selector:
matchLabels:
app: podinfo
replicas: 1
template:
metadata:
labels:
app: podinfo
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- podinfo
topologyKey: "failure-domain.beta.kubernetes.io/zone"
containers:
- name: podinfo
image: stefanprodan/podinfo
$ kubectl apply -f test.yaml
下記のように ap-northeast-1
にある nod-1
に配置されました。
次にレプリカ数を 2
に増やしてみます。
$ kubectl scale deployment.v1.apps/podinfo --replicas=2
想定通り、既に配置しているap-northeast-1a
を避けてap-northeast-1c
にある node-2
に配置されました。
さらにレプリカ数を増やしてみます。
$ kubectl scale deployment.v1.app/podinfo --replicas=3
preferredDuringSchedulingIgnoredDuringExecution
で設定しているので、既に同じAZで実行中のPodがあるものの、新しくPodが node-2
で実行されました。
この状態で ap-northeast-1d
に node-3
という新しいノードを追加してみます。
新しいノードが追加されても、kubernetesは自動でPodのリバランスは行われないため、node-2
で Podが2つ動いている状態は解消されません。
Descheduler for Kubernetes でリバランスを行う
先記のように、PodAntiAffinity を使って Pod の配置先を指定したとしても、条件によって偏って配置されてしまったPodはリバランスされないため、手動でPodを停止させるなどしないと解消されません。
descheduler を使うと CronJob として実行し、偏って配置されたPodを停止して正しいノード先に再配置されるようになります。
descheduler では PodAntiAffinity に違反したPodを停止する以外にも以下のようなポリシーを設定することもできます。
- 同じノードに重複してPodが配置されている場合に重複したPodを停止させて別ノードに配置されるようにする。
- 使用率が低いノードがある場合、他のノードのPodを停止して、このノードに配置されるようにする。
それ以外は Policy and Strategies を参照してください。
ただし、 descheduler は、あくまでもPodを停止する事しかしないため、Podの再配置については Kuberenetes が決定するため、思った通りの挙動にならないこともあります。
では、Helm でインストールできるので検証用EKSにインストールしてみます。
$ helm upgrade --install --namespace kube-system descheduler descheduler/descheduler
デフォルトでは */2 * * * *
なので、毎2分ごとに実行されます。
しばらく待つと descheduler が実行されて下記のようにPodが正しく再配置されました。
まとめ
PodAntiAffinity と descheduler を使うことで、AZ(アベイラビリティーゾーン)ごとにPodを分散して配置することができるようになりました。
descheduler を使うと、意図しないタイミングでPodが停止することになるので、Pod disruption budgets や コンテナーの Lifecycle Hook の PreStop の設定を行なっておく必要もあるかと思います。