🚧

クラスター外に公開したPodのダウンタイムを最小化する

2022/12/28に公開

ダウンタイム≒リクエストを受けられるPodがひとつもない
状態を最小化したい

環境

  • EKS
  • PodはALBやNLBを使って公開
  • AWS Load Balancer Controllerを利用
    • target-type: ipで利用
    • TargetGroupBindingを利用(Ingress使っててもあまり変わらないと思われる)

Deploymentの更新時のダウンタイムを最小化する

Deploymentの更新時にリクエストを受けられるPodがひとつもない状態を引き起こす要因は2つある。

  • ローリングアップデートの進行が速すぎてターゲットグループへの登録が間に合っていない
  • Podの終了が速すぎてターゲットグループからの登録解除が間に合っていない

ローリングアップデートの進行が速すぎてターゲットグループへの登録が間に合っていない

Deploymentのspec.strategy.rollingUpdate.maxSurgespec.strategy.rollingUpdate.maxUnavailableは25%なので、レプリカがそう多くはない状態においては
「新Podが1個作られる」→「旧Podが1個消える」 が交互に繰り返されてローリングアップデートが進行する。

デフォルトでは「新Podができた(Readyになった)」の条件に「ターゲットグループに登録完了してhealthyになった」は含まれていないため、
ターゲットグループ側にhealthyなターゲットがいないにもかかわらず旧Podがどんどん削除されていき、healthyなターゲットがひとつも存在しない瞬間=ダウンタイムが発生することがある。

KubernetesにはPodがReadyになったと判断されるための条件を追加する仕組み(Readiness Gates)があり、AWS Load Balancer Controllerはこれに対応している。

https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/deploy/pod_readiness_gate/

Namespaceにラベル付けをすることでWebhookにより自動的にNamespace内のPodに対して有効化できる。
同じNamespace内でもPodによって有効・無効を変えたい場合は、MutatingWebhookConfigurationを変更してobjectSelectorを設定する。

https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/9c3f5a27f6ae646f17f0f5c7ec36f48ebc2e69e4/helm/aws-load-balancer-controller/values.yaml#L299-L307

Podの終了が速すぎてターゲットグループからの登録解除が間に合っていない

Podが速やかに終了しすぎるとターゲットグループからの登録解除が間に合わず、「healthyなターゲットだからリクエストを転送したが、Podはもうそこにはいなかった」場合がある。

Podのspec.containers.lifecycle.preStopでsleepなどさせて終了を先延ばしにすることで、PodがServiceから外れターゲットグループからも登録解除されるまでの時間稼ぎをする。

apiVersion: apps/v1
kind: Deployment
...
spec:
...
  template:
...
    spec:
      containers:
        lifecycle:
          preStop:
            exec:
              command: ["sleep", "90"]

ノードメンテナンス時のダウンタイムを最小にする

ノードメンテナンス時≒kubectl drainが実行されたとき

  • drainを削除やメンテ予定のノード全体に実施したら、Podが一斉に終了して誰も残らなかった
  • drainを選択的に(1台ずつや1AZずつ)実施したが、Podの配置が偏っていたので結局全員いなくなった
  • Podの終了が早すぎてターゲットグループからの登録解除が間に合っていない
    • 対処は先ほどと同じなので省略

drainを削除やメンテ予定のノード全体に実施したら、Podが一斉に終了して誰も残らなかった

PodDisruptionBudgetを作って「最低○人残ってほしい」という意思表示をすることで「一斉に終了して誰も残らない」を防ぐ。

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: foo-api
  namespace: foo
spec:
  # 最低ひとりは残ってほしい
  minAvailable: "1"
  selector:
    matchLabels:
      app: foo-api

最大限削除されてもいい数をspec.maxUnavailableで指定することもできる。
minAvailablemaxUnavailable33%のように割合で書くこともできる。

https://kubernetes.io/docs/tasks/run-application/configure-pdb/

drainを選択的に(1台ずつや1AZずつ)実施したが、Podの配置が偏っていたので結局全員いなくなった

このケースも全員一気にいなくなることはPDBで防げる。

が、配置が偏らないようにもしておくと障害の備えにもなり、PDBも守られやすい(PDBを守りながら終了していくために別ノードに新たにPodを作る過程を経るより、最初からPodが散っていて円滑に終了していける方が短時間で済み、タイムリミットがあるdrainも正常終了しやすい)。

Podの配置を散らすためにTopology Spread Constraintsを利用できる。

Topology Spread Constraints

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo-api
spec:
...
  template:
    metadata:
      labels:
        app: foo-api
    spec:
...
      topologySpreadConstraints:
        # zoneによるpodの偏りを1台までとする
        - maxSkew: 1
          whenUnsatisfiable: DoNotSchedule
          topologyKey: topology.kubernetes.io/zone
          labelSelector:
            matchLabels:
              app: foo-api

https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/

Descheduler

Topology Spread Constraintsの制限事項として、「Podの配置時にしか考慮されない」点がある。

例:2つのAZにPodが3つあり、いずれのAZにも1つ以上Podがあった。HPAによりPodが2個に減ったが、その際1つしかないAZのPodが終了した=一方のAZにPodが2個、もう一方には0個に偏った

この点をカバーするためにPodを自動的に削除して再配置を促すDeschedulerが開発されている。

https://github.com/kubernetes-sigs/descheduler

Topology Spread Constraintsに反した状態になっているPodを削除するstrategyを指定するvalues.yaml例

deschedulerPolicy:
  strategies:
    # https://github.com/kubernetes-sigs/descheduler#removepodsviolatingtopologyspreadconstraint
    RemovePodsViolatingTopologySpreadConstraint:
      enabled: true
      params:
        includeSoftConstraints: false
        nodeFit: true

Discussion