OpenAICloudflare2026/03/26 13:00

A one-line Kubernetes fix that saved 600 hours a year

要点だけを先に読めるように短く再構成したセクションです。

元記事

Quick Digest

要約

要点だけを先に読めるように短く再構成したセクションです。

openaijamodel: gpt-5-mini-2025-08-07

1行のKubernetes修正で年間600時間を取り戻した

Key Points

  • fsGroupChangePolicyをOnRootMismatchに
  • 再起動30分→30秒に短縮
  • 年間約600時間を節約

Summary

Atlantisの再起動がPV上のファイル数増加により30分かかっていた原因は、pod.spec.securityContext.fsGroupとそのデフォルトポリシー(fsGroupChangePolicy: Always)で、kubeletがマウント時にファイル全体に対して再帰的に所有権変更(chgrp -R)を行っていたことです。fsGroupChangePolicyをOnRootMismatchに変更することで、再起動時間が約30分→約30秒に短縮され、月約50時間(年間約600時間)を節約できました。

Key Points

  • 検出手順
    • kubeletログで "Setting volume ownership" と関連するエラー/タイムアウトを確認。
    • Podイベントとノード(kubelet)ログを突き合わせてPVマウント後のギャップを特定。
  • 原因
    • fsGroupChangePolicyがデフォルトのAlwaysで、マウントごとに全ファイルのグループを再帰的変更していた(大量ファイルで非常に遅い)。
  • 修正(実践的) spec: template: spec: securityContext: fsGroupChangePolicy: OnRootMismatch
    • この1行追加で、ルートディレクトリに不整合がある場合のみ所有権を修正するようになる。
  • 注意点
    • fsGroupChangePolicy: OnRootMismatchはKubernetes v1.20以降で利用可能。
    • PV内のファイル生成方法が不明な場合は無闇に適用しない(ルートの権限を事前に確認する)。
  • 効果
    • 再起動時間が約30分→約30秒に短縮。月約50時間、年約600時間の工数回復。

Recommended actions

  • 大きなPVを使うワークロードはfsGroupとfsGroupChangePolicyを監査すること。
  • ノードのkubeletログとPVイベントでマウント/初期化に掛かる時間を計測し、自動化された再起動やアラートの閾値を見直すこと。

Full Translation

翻訳

原文の流れを保ったまま読める翻訳セクションです。

openaijamodel: gpt-5-mini-2025-08-07

年間600時間を救った1行のKubernetes修正

概要

Atlantis(Terraformのplan/applyを行うツール)を再起動するたびに、復旧に約30分かかっていました。これが月に約100回発生していたため、毎月50時間以上のエンジニアリング時間がブロックされ、オンコール担当者に確実にページが飛んでいました。原因は、PersistentVolume(PV)上のファイル数が数百万に達した際に、Kubernetesの安全なデフォルト設定がボトルネックになっていたことです。1行の変更で直し、年間で約600時間を取り戻せました。ここでその調査と修正の経緯を紹介します。

不思議に遅い再起動

私たちはGitLabのMerge Request(MR)で多数のTerraformプロジェクトを管理しており、Atlantisがplanとapplyとロックを担当しています。AtlantisはKubernetes上の単一インスタンスのStatefulSetとして動き、リポジトリ状態はPersistentVolume(PV)に保持します。プロジェクトのオン/オフボードやTerraformで使う認証情報の更新時にAtlantisを再起動して新しい設定を反映する必要があり、これが30分かかることがありました。

遅い再起動は、Atlantisが使う永続ストレージのinodes不足で再起動してボリュームをリサイズしたときに顕著になりました。inodesはディスク上の各ファイル・ディレクトリエントリで消費され、ファイルシステム作成時のパラメータで決まります。我々のKubernetesプラットフォームが提供するCeph実装ではmkfsにフラグを渡す方法を提供しておらず、デフォルト値に依存していました。ファイルシステムを拡張してinodesを増やす必要があり、PVを再作成するにはPodの再起動が必要でした。

アラートのウィンドウを延ばすことも検討しましたが、それは単に問題を隠して実際の障害への対応を遅らせるだけです。そこで、なぜそんなに時間がかかるのかを掘り下げて調べることにしました。

挙動の確認

Secretsの反映のためにAtlantisをローリング再起動する際、私たちは次のコマンドを実行していました:

kubectl rollout restart statefulset atlantis

既存のPodはグレースフルに終了し、新しいPodが立ち上がります。新しいPodはすぐに現れるように見えますが、状態は次のようになります:

$ kubectl get pod atlantis-0
atlantis-0   0/1   Init:0/1   0   30m

まずPodのイベントを確認します。Initコンテナの実行を待っているので、イベントログに何かヒントがあるはずです:

$ kubectl events --for=pod/atlantis-0
LAST SEEN   TYPE    REASON     OBJECT            MESSAGE
30m         Normal  Killing    Pod/atlantis-0    Stopping container atlantis-server
30m         Normal  Scheduled  Pod/atlantis-0    Successfully assigned atlantis/atlantis-0 to 36com1167.cfops.net
22s         Normal  Pulling    Pod/atlantis-0    Pulling image "oci.example.com/git-sync/master:v4.1.0"
22s         Normal  Pulled     Pod/atlantis-0    Successfully pulled image "oci.example.com/git-sync/master:v4.1.0" in 632ms ...

スケジューリングからイメージプル開始までの間に長い遅延があり、Kubernetesのイベントだけでは原因が分かりませんでした。

深掘り(kubeletログ)

ノード上でPodの作成やPVのマウントなどを担当するkubeletはsystemdサービスとして動いており、ログはKibanaで参照できます。Podがスケジュールされたノード名と関連オブジェクトを使ってログをフィルタすると、AtlantisのPVがマウントされる様子やSecretボリュームのマウントは問題なく行われているのが見えました。しかし、ログに大きな空白(遅延)があります。

kubeletログの一部は次の通りです:

[operation_generator.go:664] "MountVolume.MountDevice succeeded for volume \"pvc-94b75052-8d70-4c67-993a-9238613f3b99\" ..." pod="atlantis/atlantis-0"
[pod_workers.go:1298] "Error syncing pod, skipping" err="unmounted volumes=[atlantis-storage], unattached volumes=[], failed to process volumes=[]: context deadline exceeded" pod="atlantis/atlantis-0"
[util.go:30] "No sandbox for pod can be found. Need to start a new one" pod="atlantis/atlantis-0"

これらのメッセージが数回ループした後で、ようやくPodが正常に起動しました。kubeletは他の準備が整っていると判断しているように見えますが、何かがタイムアウトしてPodが起動されていませんでした。

決め手(PVのログ)

最後に残った手掛かりはPVそのものです。PV名をKibanaで検索すると、すぐに次のログが目に入りました:

[volume_linux.go:49] Setting volume ownership for /state/var/lib/kubelet/pods/.../volumes/kubernetes.io~csi/pvc-94b75052-.../mount and fsGroup set. If the volume has a lot of files then setting volume ownership could be slow, see https://github.com/kubernetes/kubernetes/issues/69699

冒頭でinodesが尽きたことを述べましたが、つまりPV上に大量のファイルがありました。PVをマウントすると、kubeletはfsGroupが設定されている場合にファイルシステム全体を再帰的に走査してchgrp -Rのようにグループ権限を再設定していました。ファイル・フォルダが大量にあるとこれは非常に時間がかかります。

Podのspec.securityContextには fsGroup: 1 が含まれていました。これはGID 1で実行されるプロセスがボリュームのファイルにアクセスできるようにするためです。Atlantisは非rootユーザーで動作するため、この設定がないとPVを読んだり書いたりできません。Kubernetesはこの要件を満たすために、PVをマウントするたびにファイルシステム全体の所有権を再帰的に更新していました。

修正

解決は単純でした。Kubernetes v1.20以降、pod.spec.securityContextfsGroupChangePolicy というフィールドが追加されています。このフィールドのデフォルトは Always で、今回見たような挙動になります。もう一つの値 OnRootMismatch は、PVのルートディレクトリに正しい権限がない場合のみ変更を行います。

PV上のファイル生成方法が明確でない場合は fsGroupChangePolicy: OnRootMismatch を安易に設定しないよう注意が必要です。私たちはPV内でグループが変更されるべき要因がないことを確認した上で、次のように設定しました:

spec:
  template:
    spec:
      securityContext:
        fsGroupChangePolicy: OnRootMismatch

この変更により、Atlantisの再起動時間は約30分から約30秒に短縮しました。

教訓と提言

  • Kubernetesのデフォルトは小規模なボリューム向けには安全で妥当ですが、データが増えるとボトルネックになります。
  • 大きなPVを扱うワークロードでは、再帰的なパーミッション変更(fsGroupやfsGroupChangePolicy関連)が再起動時間を食っていないか確認してください。
  • fsGroupChangePolicy は v1.20 から利用可能です。必要に応じて OnRootMismatch を検討してください。

すべての修正が大がかりである必要はなく、多くの場合「なぜシステムがそう振る舞うのか?」と問うことが重要です。

もし大規模インフラのデバッグに興味があるなら、私たちは採用しています。Cloudflare Community や Discord で話しましょう。


Cloudflareの接続クラウドは企業ネットワーク全体を保護し、インターネット規模のアプリケーション構築を効率化し、あらゆるウェブサイトやインターネットアプリケーションを高速化し、DDoS攻撃から守り、ゼロトラストへの道を支援します。任意のデバイスから 1.1.1.1 にアクセスして、インターネットをより速く安全にする無料アプリを始めてください。ミッションの詳細については start here をご覧ください。キャリアの方向性を探している場合は open positions を確認してください。

タグ: server-island-start Kubernetes Terraform Platform Engineering Infrastructure SRE