OpenAICloudflareMar 26, 2026, 1:00 PM

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

A condensed section focused on the key takeaways first.

Original Post

Quick Digest

Summary

A condensed section focused on the key takeaways first.

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

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

Key Points

  • 30min → 30s restarts
  • set fsGroupChangePolicy: OnRootMismatch
  • ~600 hours/year reclaimed

Summary

A Kubernetes default caused kubelet to recursively change ownership on a large PersistentVolume (millions of files) during pod startup, making Atlantis restarts take ~30 minutes. Changing pod.spec.template.spec.securityContext.fsGroupChangePolicy from the default Always to OnRootMismatch reduced restart time to ~30s and reclaimed ~50 hours/month (~600 hours/year).

Key Points

  • Symptom: Atlantis StatefulSet restarts hung in Init:0/1 for ~30 minutes; kubelet logs showed context deadline exceeded after mounting the PV.

  • Root cause: with fsGroup set, kubelet's default fsGroupChangePolicy: Always runs a recursive ownership change (chgrp -R) across the entire PV, which is very slow on filesystems with millions of entries.

  • Diagnosis tips: check pod events, then kubelet logs on the node (search for the PV name and messages like "Setting volume ownership" and "context deadline exceeded").

  • Fix (one line): set fsGroupChangePolicy: OnRootMismatch in the pod template securityContext. Example YAML:

    spec: template: spec: securityContext: fsGroupChangePolicy: OnRootMismatch

  • Impact: restart time reduced from ~30 minutes to ~30 seconds for Atlantis; reclaimed ~50 hours/month (~600 hours/year) of blocked engineering time.

  • Recommendations: audit securityContext on workloads with large PVs (check fsGroup and fsGroupChangePolicy), prefer OnRootMismatch when you control PV ownership or know file ownership, and monitor kubelet logs for slow mounts.

Full Translation

Translations

A translation section that keeps the flow of the original article.

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