バイセル Tech Blog

バイセル Tech Blogは株式会社BuySell Technologiesのエンジニア達が知見・発見を共有する技術ブログです。

バイセル Tech Blog

Kubernetes の Rolling Update の落とし穴 〜replicas の値は余裕を持って設定しよう〜

はじめに

こちらは バイセルテクノロジーズ Advent Calendar 2023 の8日目の記事です。 前日の記事は那仁さんの運用プロダクトのDataflowをBigQueryサブスクリプションに移行した話でした。

SRE部の大舘です。 バイセルで、とあるプロダクトのインフラを担当しています。

このプロダクトではインフラとして Google Cloud の GKE(Google Kubernetes Engine) を用いてシステムを運用しています。

今回は Kubernetes の Rolling Update が想定通りに動かなかったこと、その原因を調査して、解決方法を見つけた時の話をしたいと思います。

背景

このプロダクトではこれまで、新しいバージョンのリリースは、業務が終わった後、利用者がいない時間に行っていましたが、最近業務中でもリリースを行うようになりました。

ある日、本番環境のプロダクトに新しいバージョンの Docker イメージをデプロイしたら、5分間ほどプロダクトにアクセスできなくなってしまいました。

調査した結果、デプロイ前には12個起動していた Pod が一時的に1個になってしまい、デプロイ後の Pod の数を元々あった12個の状態まで戻すのに時間がかかっていて、この間アクセスできなくなっていたことがわかりました。

Kubernetes の Deployment のデフォルトのデプロイ方法は Rolling Update なので、元の Pod の数を維持しつつ、順番に Pod を新しいバージョンに置き換えてくれるものと思っていましたが、実際はそうではありませんでした。

何故デプロイのタイミングで Pod が1個になってしまったのか、想定通りの Rolling Update の動作をさせるためにはどうすればいいのか、 調査して解決方法を見つけたので、紹介したいと思います。

Rolling Update の挙動

Rolling Update は、バージョンAが動作する Kubernetes クラスタにバージョンBをデプロイする時に、 全体の Pod の数を保ったまま徐々にバージョンAからバージョンBに切り替えていくデプロイ手法です。

Kubernetes クラスタの Rolling Update はデフォルトで次のように設定されています。

apiVersion: apps/v1
kind: Deployment
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%

maxSurge は理想状態の Pod 数を越えて作成できる最大の Pod 数で、maxUnavailable は利用不可能となる最大の Pod 数を示します。

デフォルトでは maxSurge, maxUnavailable 共に 25% に設定されています。

上記の図の例だと、4個の Pod があるので、1個の Pod が現在の4個に加えて作成可能で、1個の Pod が利用不可能でも許容されます。 なので、Pod は1個ずつ古いものが消され、新しいものが作成され、結果として全体で4個の Pod 数を維持することができます。

プロダクトに5分間アクセスできなかった時の Pod 数の推移

プロダクトの Kubernetes はデプロイ戦略に特別な設定をしていなかったので、デフォルトの Rolling Update の設定である maxSurge, maxUnavailable 共に起動中の Pod 数の 25% を期待しましたが、Pod の数を保ったままデプロイがされませんでした。 以下は Pod 数の推移となります。

  • デプロイ前、バージョンAの Pod は12個起動していた
  • デプロイした瞬間、バージョンAの Pod を1個残し、11個の Pod を消した
  • バージョンAの Pod が1個の状態で、バージョンBの Pod を1個作った
  • バージョンBの Pod を作成したら、1個だったバージョンAの Pod を消した
  • その後、バージョンBの Pod は6個になり、次にまた6個作成され、Pod の数は12個に戻った

12個の Pod でアクセスを捌いていたのに、一時的に Pod が1個になってしまい、アクセスを捌き切れなくなってしまい、プロダクトにアクセス不能になっていました。 デプロイ前に12個の Pod が起動していたので、デプロイ中も Pod を12個に保ってほしかったのですが、期待した動作をしていませんでした。

この時、Deployment の replicas の数が 1 に設定されていました。

apiVersion: apps/v1
kind: Deployment
spec:
  replicas: 1

後になってわかったのですが、Deployment の replicas の数が 1 に設定されていたのが、上記の挙動になっている原因でした。

Kubernetes の公式ドキュメント には次のように記載されているのですが、これは Deployment の replicas に設定した数に対してで、HorizontalPodAutoscaler によって調整された Pod の数に対してではありませんでした。

Deploymentは、Podが更新されている間に特定の数のPodのみ停止状態になることを保証します。デフォルトでは、目標とするPod数の少なくとも75%が稼働状態であることを保証します(25% max unavailable)。

なお、Deployment には HorizontalPodAutoscaler の設定を以下のようにしていて、 maxReplicas を 30 にしているので、Deployment の replicas の数が 1 でも12個の Pod が生成可能になっている状態でした。

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: foo
  namespace: default
spec:
  maxReplicas: 30
  metrics:
  - resource:
      name: cpu
      target:
        averageUtilization: 70
        type: Utilization
    type: Resource
  - resource:
      name: memory
      target:
        averageUtilization: 70
        type: Utilization
    type: Resource
  minReplicas: 6
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: foo-deployment

上記の設定では、Pod 全体の CPU 使用率が 70% を越えた、もしくは Pod 全体のメモリ使用率が 70% を越えた時に、新しい Pod が生成されるようになっています。

開発環境でデプロイ時に Pod 数が1になってしまう現象の再現

開発環境でも Deployment の replicas の数は1に設定されていました。

apiVersion: apps/v1
kind: Deployment
spec:
  replicas: 1

また、HorizontalPodAutoscaler は以下のように設定されていて、最大で20個まで Pod が生成可能になっていました。

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: foo
  namespace: default
spec:
  maxReplicas: 20
  metrics:
  - resource:
      name: cpu
      target:
        averageUtilization: 70
        type: Utilization
    type: Resource
  - resource:
      name: memory
      target:
        averageUtilization: 70
        type: Utilization
    type: Resource
  minReplicas: 1
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: foo-deployment

まず、開発環境にアクセスを集中させて Kubernetes クラスタに負荷をかけ、Pod 数を増やしてみました。 集中アクセスには、負荷テストなどでよく使われる ab(Apache Bench) を使いました。

次のようにして、同時アクセス数200、合計実行回数5万回で ab から開発環境の Kubernetes クラスタに負荷をかけ続けたところ、 通常1個しか起動していない Pod 数を4個まで増やすことができました。

ab -n 50000 -c 200 https://development-endpoint/path/to/api

この状態でデプロイしてみると、本番環境同様に Pod を1個残して全て消し、新しい Pod を1個作成し、残っていた古い1個の Pod を消し、その後に3個新しい Pod を作成する、という挙動が再現できました。

Deployment の replicas の数を4にしてみる

現状の設定では、デプロイ時に起動している Pod の数を保ったまま Rolling Update できないことがわかりました。

ここで、Deployment の replicas の数を1から4に上げてみることにしました。

apiVersion: apps/v1
kind: Deployment
spec:
  replicas: 4

ab で負荷をかけ続けながらデプロイすると、今度は Pod を4個に保ちながらデプロイすることができました。

maxSurge 25%、maxUnavailable 25% の通り、1つずつ古い Pod から新しい Pod に入れ変わりました。

この状態で ab を終了し負荷をかけるのをやめたら、4個あった Pod が1個にまで減りました。

HorizontalPodAutoscaler の minReplicas の数を1に設定しているのが効いているのがわかります。

つまり、最低限起動していてほしい Pod の数は HorizontalPodAutoscaler の minReplicas に設定し、デプロイ時に常時起動していてほしい Pod の数は Deployment の replicas に設定しておけばよいことがわかりました。

利用不可能な Pod 数を1個も許容せずに Rolling Update させる

Kubernetes のデフォルトの Rolling Update 設定だと maxUnavailable 25% で、4個の Pod があったら、そのうち1個の Pod は利用不可能でも許容されます。

しかし、プロダクトにおいては、Rolling Update に求めたいことは

  • デプロイ時に Pod 減少によるトラブルを避けたい
  • 異なるバージョンのイメージが同時に稼働する時間を極力少なくしたい
  • デプロイをなるべく早く終わらせたい

の3点です。

そこで、maxSurge 100%, maxUnavailable 0% に設定して、Deployment の replicas に指定してある数だけ新しい Pod を作成可能に、利用不可能な Pod 数を0にする設定を入れました。

なお、この設定を実現するためには、Deployment の replicas に指定してある数の倍の Pod を生成できるだけ Node にリソースの余裕がある必要があります。

apiVersion: apps/v1
kind: Deployment
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 100%
      maxUnavailable: 0%

この状態で ab で負荷をかけながらデプロイしたところ、1個も利用不可能な Pod がある状態にならずに、Pod を入れ替えることができました。

まとめ

今回の調査の結果、Kubernetes の Rolling Update が想定通りに動かなかった原因は、Deployment の replicas に1が設定されていたためであることがわかりました。

Kubernetes の Rolling Update を期待通りに動作させたい場合、Deployment の replicas の数を、デプロイ中に想定している Pod 数に合わせておく必要があることがわかりました。

また、HorizontalPodAutoscaler の minReplicas に、最低限起動しておいてほしい Pod 数を指定しておけば、Deployment の replicas に指定した数の Pod に余剰がある場合には、負荷状況に合わせて Pod を減らしてくれることがわかりました。

Kubernetes の Rolling Update が思うように動かない場合、Deployment の replicas の値を見直してみると良いでしょう。

最後に、バイセルではエンジニアを随時募集しております。興味のある方はぜひ以下の採用サイトをご覧ください。 herp.careers

明日のバイセルテクノロジーズ Advent Calendar 2023は金澤さんのAuth0とEntra IDを扱うプロダクト同士を繋げるためのIstio設定あれこれです、そちらもぜひ併せて読んでみてください!