😇

あれ、本番環境のKubernetes Podがいなくなっちゃったよ

2023/12/19に公開

はじめに

とある日、本番環境のKubernetes Pod数が0になってしまう事態が発生しました。この記事はそのストーリーを語っています。

Kubernetes運用の現状

私たちのプロダクトはリリースから10周年を迎えており、溜まった技術負債を脱却すべく、インフラとアプリケーションのリプレイス中です。

  • インフラはオンプレミスからAWSクラウド(コンピューティングはEKS)
  • アプリケーションはVBScript+jQueryからRails+Next.js

リプレイスはまさに過渡期であります。段階的に移行しているため、日に日に、Kubernetes Podを増やしていました。

chapter1. アラートは突然に

そんなある日、「502エラーめっちゃ出てるくね!? てか、リリース時と被ってんじゃん」

この原因はすぐに分かりました。私たちのチームでは、デプロイ戦略としてローリングアップデートを採用しています。Deploymentのデフォルト設定では、起動中のPod数の25%ごと入れ替わるため、仮に50台で運用しているとしたら 13台のPodが一気に減少してしまいます。

なんと!これはいかん。常時起動しているPod数が増加していくにつれて、ローリングアップデート時に減少するPodも増えてしまいます。

spec:
  replicas: 50
  selector:
    matchLabels:
      app: rails
  strategy:
    type: RollingUpdate

そこで、maxSurge: 100%maxUnavailable: 0%とすることで解決しました。つまり、replicasで指定したPod数が減少することなく、Pod数を一気に倍に増加させることで一時的にリソースは倍になるが安全にリリースできるようになります。

spec:
  replicas: 50
  selector:
    matchLabels:
      app: rails
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 100%
      maxUnavailable: 0%

これで新しいPodが完全に立ち上がるまで古いPodたちは残ってくれる...

chapter2. HPAが悪さをする

安心したのもつかの間、またリリース時に502エラーが出る現象が発生してしまいました。直したのはずなのに...。よく見るとまたPod数が減っている。なぜ?

結論から言うと、これはHPAが関係ありました。

Deploymentでは、replicas: 50と設定し、HPAでは以下のマニフェストのように設定していました。リリースのタイミングはちょうどピークタイムであったこともあり、HPAによってreplicasが上書きされ、Pod数が70まで上昇していました。

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: rails
  namespace: rails
spec:
  minReplicas: 30
  maxReplicas: 70
...

この状態では、リリースによって以下の挙動になってしまいます。

  1. リリース前に70個のPodが起動
  2. リリースによって、HPAでスケールした分の20個のPodが削除され、50個のPodでローリングアップデートが開始
  3. Pod数の減少により負荷が高まり、HPAによってスケールアウトし元に戻る

スケールアウトした分も含めてローリングアップデートしたいですよね。そこで調べてみると、どうやら「Deploymentのreplicasフィールドを削除して、HPAのみにreplicasをコントロールさせると良い」という情報が。

chapter3. 俺はreplicasの存在を消したい

俺はreplicasの存在を消したい。そのことに必死でした。そして、replicas: 50を消し、Pull Requestを作成しました。

spec:
  selector:
    matchLabels:
      app: rails
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 100%
      maxUnavailable: 0

よし、Approveされた。いざ、staging、productionに適用するぜよ。

chapter4. さらば愛するPodたち

言うことはありません。タイトルの通りです。一応、念の為リリースの様子をArgo CDダッシュボードで確認していましたが、愛するPodたちはあっという間にkillされてしまいました。

Podは1個残りましたが、今度はそのPodにアクセスが集中したためヘルスチェックに引っかかりDownしてしまいました。

真っ赤に染まるアラートと、鳴り響くPargerDuty

chapter5. なぜ愛するPodたちは出ていったの

そもそも、kubectl applyコマンドは「適用するマニフェスト」と「前回適用したマニフェスト」を比較して、その差分を変更として適用します。

適用前のマニフェストの状態は、last-applied-configurationです。last-applied-configurationにreplicas: 50が残っていると、replicasフィールドを削除しただけではデフォルトの1が適用されてしまい。Podが1個になってしまいます。

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {... spec":{"replicas":50,"selector":{"matchLabels": { ...

chapter6. どうすれば愛するPodは出ていかないの

事前にlast-applied-configurationからreplicasフィールドを削除しておけば良いです。そうすることで、「適用するマニフェスト」と「前回適用したマニフェスト」にreplicasの設定がないので、マニフェスト適用時に変更されなくなります。

おわりに

ふざけたタイトルを付けていますが、私たちインフラエンジニアは常に危険と隣り合わせです。作業の影響範囲を知り、動作確認をきちんと行い、失敗してもロールバックの準備をしておかなければなりません。それらを怠ると取り返しのつかないことになってしまいます。僕のようにならないように気をつけて仕事しましょう。

株式会社ZOZO

Discussion