技術深掘り

実践 Kubernetes 本番運用ガイド (2025 年版)

SRE/インフラエンジニア向け。 Linux カーネルレベルの挙動から、リソース管理、可用性、セキュリティ、コスト最適化まで、本番環境で求められる Kubernetes の高度な知識と実践ノウハウを徹底解説。

佐藤 裕介

佐藤 裕介

大規模サービスのインフラ運用経験 10 年以上。 Kubernetes 、 Terraform 、 CI/CD パイプライン構築を得意とし、信頼性の高いシステム基盤を提供します。

Kubernetes SRE Linux cgroups Observability Security GitOps
実践 Kubernetes 本番運用ガイド (2025 年版)

はじめに: 本番運用の「壁」を越えるために

Kubernetes は現代のクラウドネイティブアプリケーションにおける OS となりました。しかし、その強力さと引き換えに、本番環境で安定稼働させるための運用は複雑を極めます。開発環境で kubectl apply が通ることと、本番環境で SLO/SLA を達成し続けることは、全く別のスキルセットを要求します。

もしあなたが、以下の項目にピンときたら、この記事はあなたに必要な記事かもしれません。

  • 予期せぬ Pod の再起動に悩まされている
  • requestslimits を設定したにも関わらずパフォーマンスのばらつきに首を傾げている
  • 障害発生時に根本原因の特定が困難だと感じている

このガイドは、単なるベストプラクティスの羅列ではありません。 SRE やインフラエンジニアが「なぜ」その設定が必要なのかを理解し、トレードオフを考慮した上で技術的判断を下せるようになることを目的としています。 Linux カーネルの cgroups やスケジューラの挙動といった低レイヤーの知識から、 GitOps による宣言的なクラスタ管理といった高レイヤーの運用論まで、 Kubernetes を本番環境で「飼いならす」ための精緻な知識を提供します。

この記事を読み終える頃には、あなたは以下の問いに自信を持って答えられるようになっているでしょう:

  • CPU limits を設定することが、なぜ Java アプリケーションで予期せぬレイテンシを引き起こすのか?
  • Pod Anti-AffinitytopologySpreadConstraints は、どのように使い分けるべきか?
  • HPA と VPA が競合する問題に、どのように対処すべきか? Cluster Autoscaler の代替として Karpenter を検討すべきなのはなぜか?
  • default Namespace を放置することが、なぜ深刻なセキュリティリスクとなるのか?
  • CrashLoopBackOff の根本原因を、 kubectl debug を使ってどのように特定するのか?

これらの問いに対する答えは、単純な設定変更では得られません。 Kubernetes の内部動作、 Linux カーネルの挙動、そしてアプリケーションの特性を総合的に理解する必要があります。本ガイドと共に、 Kubernetes 運用の深淵を探求し、盤石な本番環境を構築するための確かな知見を手に入れましょう。


目次

  1. 第1章: リソース管理の深層
  2. 第2章: 絶対的な可用性の追求
  3. 第3章: インテリジェントなスケーラビリティ
  4. 第4章: 多層防御セキュリティ
  5. 第5章: オブザーバビリティとSLO
  6. 第6章: 高度なトラブルシューティング
  7. 第7章: 宣言的クラスタ管理 (GitOps)
  8. まとめ

1. リソース管理の深層

Kubernetes におけるリソース管理は、単に YAML に数値を設定する作業ではありません。 Linux カーネルの cgroups とスケジューラが、コンテナのパフォーマンスと安定性にどのように影響するかを理解することが本質です。

Linux cgroups とコンテナリソース

Kubernetes が Pod のリソースを制御する際、内部的には Linux カーネルの**Control Groups (cgroups)**機能を使用しています。

  • spec.resources.requests.cpu: cpu.shares cgroup に変換されます。これは CPU 時間の相対的な重み付けです。ノードの CPU が競合している場合にのみ意味を持ちます。
  • spec.resources.limits.cpu: cpu.cfs_quota_uscpu.cfs_period_us に変換されます。これは CPU 時間の絶対的な上限です。例えば、 500m は、 100ms (cfs_period_us) の期間中に 50ms (cfs_quota_us) の CPU 時間しか使用できないことを意味します。
  • spec.resources.requests.memoryspec.resources.limits.memory: どちらも memory.limit_in_bytes cgroup に影響します。 requests はスケジューリング時の判断に使われ、 limits はコンテナのメモリ使用量のハードリミットとして設定されます。

この知識は、なぜ requests だけを設定してもパフォーマンスが安定しないことがあるのか、なぜ limits がスロットリングを引き起こすのかを理解する上で不可欠です。

QoS クラスとカーネルの挙動

Kubernetes は Pod を 3 つの QoS (Quality of Service)クラスに分類し、リソースが逼迫した際のカーネルの挙動を決定します。

QoS クラス条件oom_score_adjeviction (立ち退き) 優先度
Guaranteed全コンテナで requests == limits が設定されている-998最も低い
Burstablerequestslimits が異なる、または一部未設定2-999
BestEffort全コンテナでリソースが未設定1000最も高い
  • oom_score_adj: Linux カーネルがメモリ不足時にどのプロセスを終了させるか (OOM Kill) を決定するためのスコアです。スコアが低いほど、最後まで生き残ります。 Guaranteed な Pod は、システムプロセスを除けば最も OOM Kill されにくい存在です。
  • eviction: kubelet がノードのリソース(メモリ、ディスク)不足を検知した際に、 Pod を強制的に停止させるプロセスです。 BestEffort の Pod が最初のターゲットになります。

SRE としての判断: 本番環境の重要なワークロード(データベース、 API サーバーなど)は、予測可能性と安定性を最大化するために、必ず Guaranteed クラスで実行すべきです。一方、重要度の低いバッチ処理などは BurstableBestEffort でリソース効率を高める、という判断が可能になります。

以下の図は、リソースが逼迫した際の各 QoS クラスの挙動を視覚的にまとめたものです。

graph TD
    subgraph Node["リソース逼迫時"]
        subgraph Eviction["Eviction (Kubelet による立ち退き)"]
            A["BestEffort Pod"] --> B["Burstable Pod"] --> C["Guaranteed Pod"]
        end
        subgraph OOM["OOM Kill (カーネルによる強制終了)"]
            D["BestEffort Pod"] --> E["Burstable Pod"] --> F["Guaranteed Pod"]
        end
    end

CPU Throttling の罠

CPU limits を設定することは、ノイジーネイバー問題を防ぐための基本的なプラクティスです。しかし、これが予期せぬパフォーマンス劣化を招くことがあります。特に、 JVM ( Java )や Go のように、 GC (ガベージコレクション)や多数の goroutine で短期的に CPU をバーストさせるランタイムは影響を受けやすいです。

問題のシナリオ:

  1. コンテナが CPU limits に設定された上限値に達する。
  2. カーネルは CFS スケジューラを介して、そのコンテナへの CPU 割り当てを完全に停止 (throttle) します。
  3. コンテナ内の全スレッドが停止し、アプリケーションの応答が著しく遅延(レイテンシのスパイク)。
  4. Prometheus メトリクス container_cpu_cfs_throttled_seconds_total が急増します。

対策とトレードオフ:

  • 対策 1: limits を外す、または非常に大きくする。
    • メリット: スロットリングによるレイテンシを回避できる。
    • デメリット: ノイジーネイバー問題のリスクが高まる。ノード全体のリソースを使い切り、他の Pod に影響を与える可能性がある。
  • 対策 2: requestslimits を同じ値 (Guaranteed QoS) にする。
    • メリット: パフォーマンスが予測可能になる。スロットリングのリスクは残るが、常に一定量の CPU が保証される。
    • デメリット: リソース効率が低下する。アプリケーションが常に要求した CPU 量を消費するわけではないため、無駄が生じる。
  • 対策 3 (上級): static CPU Manager Policy を使用する。
    • Guaranteed QoS の Pod に対して、特定の CPU コアを専有させることができます。これにより、 CFS スケジューラの競合やコンテキストスイッチから完全に解放され、超低レイテンシを実現できます。
    • デメリット: ノードのリソースが断片化しやすく、運用が複雑になる。リアルタイム処理など、極端なパフォーマンスが要求される場合に限定して使用すべきです。

リソース設定のサイジング戦略

「適切なリソース量」を決定するのは永遠の課題です。経験則も有効ですが、よりデータに基づいたアプローチを取るべきです。

  1. VPA (Vertical Pod Autoscaler) を updateMode: "Off" で運用する: VPA は Pod のリソース使用状況を監視し、推奨値を提示してくれます。 "Off" モードでは自動で Pod を再作成しないため、安全に推奨値のみを取得し、それを自分たちの requests 設定の参考にすることができます。
  2. OSS ツールを活用する: Goldilocks のようなツールは、 VPA の推奨値を集約し、ダッシュボードで分かりやすく可視化してくれます。これにより、全社的にリソース設定を最適化する活動が容易になります。
  3. 負荷試験: 特に新規アプリケーションでは、本番投入前に負荷試験を行い、リソース使用量の傾向(例: RPS と CPU 使用率の相関)を把握することが不可欠です。

2. 絶対的な可用性の追求

可用性は冗長性だけでは達成できません。障害をいかに迅速に検知し、安全に回復させるか、そして Pod をいかに賢く分散させるかが重要です。

高度なスケジューリング: topologySpreadConstraints

PodAntiAffinity は「同じノードに置かない」という単純なルールですが、 Pod が少数のノードに偏ってしまう可能性があります。例えば、 3 つの AZ に 9 つのノードがある場合、 Pod が AZ-A の 3 ノードに集中し、 AZ-B, C には配置されない、という状況が起こり得ます。

topologySpreadConstraints は、より高度な分散を実現します。以下の図は、 PodAntiAffinitytopologySpreadConstraints の挙動の違いを示しています。

graph TD
    subgraph "Anti-Affinity (hostname): ノードに偏る可能性"
        subgraph "AZ-A"
            N1["Node 1"] -- "Pod" --> P1((Pod))
            N2["Node 2"] -- "Pod" --> P2((Pod))
            N3["Node 3"] -- "Pod" --> P3((Pod))
        end
        subgraph "AZ-B"
            N4["Node 4"]
            N5["Node 5"]
            N6["Node 6"]
        end
    end

    subgraph "topologySpreadConstraints (zone, maxSkew: 1): ゾーン間で均等分散"
        subgraph "AZ-A "
            N7["Node 7"] -- "Pod" --> P4((Pod))
        end
        subgraph "AZ-B "
            N8["Node 8"] -- "Pod" --> P5((Pod))
        end
        subgraph "AZ-C"
            N9["Node 9"] -- "Pod" --> P6((Pod))
        end
    end
# Pod をゾーン(AZ)とノードの両方で均等に分散させる
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: myapp
      - maxSkew: 1
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: myapp
  • maxSkew: 同じトポロジキー(例: 同じ AZ )に存在する Pod 数の最大差。 1 に設定すると、 Pod ができるだけ均等に分散されます。
  • topologyKey: 分散の単位 (zone, hostname など)。
  • whenUnsatisfiable: 制約を満たせない場合の挙動。 DoNotSchedule は厳格に制約を守り、 ScheduleAnyway は努力目標とします。本番環境では DoNotSchedule が推奨されます。

ヘルスチェックのアンチパターン

正しく設計されていないヘルスチェックは、可用性を高めるどころか、連鎖的な障害を引き起こす原因となります。

  • アンチパターン 1: Liveness Probe で外部依存性をチェックする
    • 問題: データベースや外部 API の障害が、無関係なアプリケーション Pod の再起動ループを引き起こします。 DB が落ちただけなのに、 API サーバーまで再起動し始め、事態を悪化させます。
    • 対策: livenessProbe はコンテナ内部の状態(プロセスが生きているか、応答できるか)のみをチェックすべきです。外部依存性のチェックは readinessProbe で行い、準備ができていなければトラフィックから切り離す、という挙動が正解です。
  • アンチパターン 2: ヘルスチェックのタイムアウトが短すぎる
    • 問題: GC や一時的な高負荷でヘルスチェックがタイムアウトし、 Pod が不必要に再起動されます。
    • 対策: timeoutSeconds は、アプリケーションが 99 パーセンタイルで応答できる時間よりも十分に長く設定すべきです。 periodSeconds × failureThreshold が、実際に障害と判断するまでの時間となります。

安全な終了処理: preStopterminationGracePeriodSeconds

Pod が削除される際、 Kubernetes は以下のプロセスをたどります。

  1. Pod の状態が Terminating になる。
  2. readinessProbe が失敗と見なされ、 Service のエンドポイントから Pod が削除される。
  3. preStop フックが実行される。
  4. SIGTERM シグナルがコンテナに送信される。
  5. terminationGracePeriodSeconds (デフォルト 30 秒) が経過するのを待つ。
  6. 期間内にコンテナが終了しない場合、 SIGKILL シグナルで強制終了される。

重要な点:

  • terminationGracePeriodSeconds は、エンドポイントからの削除、 preStop の実行、 SIGTERM による終了処理の全ての時間を含みます
  • preStop フックで sleep を入れるのは、エンドポイントから Pod が削除され、新しいリクエストが来なくなるまでの時間を稼ぐための一般的なプラクティスです。しかし、この sleep 時間が terminationGracePeriodSeconds の大半を消費してしまうと、本来のグレースフルシャットダウン処理( DB コネクションのクローズなど)の時間がなくなり、データ不整合の原因となります。
# 良い設定例
containers:
- name: app
  lifecycle:
    preStop:
      exec:
        # Ingress Controller がエンドポイント情報を更新するのを待つ
        command: ["/bin/sh", "-c", "sleep 10"]
spec:
  # preStop(10s) + アプリケーションの終了処理(最大 20s)を考慮
  terminationGracePeriodSeconds: 30

PDB とクラスターアップグレードの相互作用

PodDisruptionBudget (PDB) は、自発的な中断(例: kubectl drain によるノードメンテナンス)からアプリケーションを守ります。しかし、設定を誤ると、クラスター管理者の作業を妨害する可能性があります。

  • シナリオ: minAvailable: 100% (または maxUnavailable: 0) と設定された PDB が存在する。
  • 問題: クラスター管理者がノードを drain しようとしても、 Pod を退避させることができないため、 drain が永遠に終わりません。これにより、クラスターのバージョンアップグレードやノードプールの入れ替え作業がブロックされます。
  • SRE としての判断: PDB はアプリケーションの可用性を守るための重要なツールですが、インフラ全体の運用と協調する必要があります。アプリケーションチームは、少なくとも 1 つの Pod の停止は許容する PDB (maxUnavailable: 1 など) を設定し、インフラチームが安全にメンテナンスを行えるようにすべきです。 SLA と PDB の設定は密接に関連しています。

3. インテリジェントなスケーラビリティ

Kubernetes のスケーラビリティは、 HPA (Horizontal Pod Autoscaler), VPA (Vertical Pod Autoscaler), Cluster Autoscaler (CA) という 3 つのコンポーネントの連携によって実現されます。しかし、これらの連携は複雑で、しばしば予期せぬ挙動を引き起こします。

HPA のチューニング: “Flapping”を防ぐ

HPA は、 Pod の CPU やメモリ使用率などのメトリクスに基づいて、 Pod のレプリカ数を自動で増減させます。

アンチパターン: CPU 使用率のみをトリガーにする

  • 問題: アプリケーションが I/O バウンド(例: データベースへのクエリがボトルネック)の場合、 CPU 使用率は低いままで、実際の負荷(例: リクエストキューの長さ)を反映しません。結果として、負荷が高まってもスケールアウトせず、レイテンシが悪化します。
  • 対策: Prometheus Adapter などを利用し、アプリケーション固有のカスタムメトリクス(例: requests_per_second, queue_length )でスケールさせるべきです。
# カスタムメトリクス(1 秒あたりのリクエスト数)でスケールする HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "100" # 1Pod あたり秒間 100 リクエストを目標

“Flapping” (フラッピング)問題: 負荷が頻繁に変動する場合、 HPA がスケールアウトとスケールインを短時間で繰り返し、システム全体が不安定になることがあります。これをフラッピングと呼びます。

  • 対策: behavior フィールドでスケールダウンの挙動を安定させます。以下の例では、スケールダウンは過去 5 分間(stabilizationWindowSeconds: 300)で最も高い推奨値に基づいて行われ、 1 分間に 1Pod しかスケールダウンしないように制限しています。
# スケールダウンを安定させる HPA
behavior:
  scaleDown:
    stabilizationWindowSeconds: 300
    policies:
    - type: Pods
      value: 1
      periodSeconds: 60

HPA と VPA: 役割分担と共存戦略

VPA は Pod の CPU/Memory の requests 値を自動で調整しますが、Pod を再起動する必要があるという重大な欠点があります。そのため、本番環境のステートレスアプリケーションで Auto モードを使うのはリスクが高いです。

SRE としてのベストプラクティス:

  1. VPA は updateMode: "Off" で運用する: VPA を推奨モードでデプロイし、 requests の最適値を継続的に監視・分析します。 VPA は Pod を再起動せず、推奨値だけを提示してくれます。
  2. 推奨値を分析し、手動で Deployment に適用する: VPA の推奨値(例: 95 パーセンタイル値)を参考に、 Deployment の requests 値を定期的に更新します。これにより、 Pod の密度が最適化され、コスト削減に繋がります。
  3. HPA で水平スケーリングを行う: 最適化された requests 値を持つ Pod を、 HPA で負荷に応じて水平スケーリングさせます。

この「 VPA で分析し、 HPA でスケールさせる」という組み合わせが、現在最も安全かつ効率的な戦略です。

次世代ノードオートスケーラー: Karpenter

Cluster Autoscaler (CA) は、スケジュール不能な Pod を検知して新しいノードを追加しますが、以下の課題があります。

  • 反応が遅い: スケジュール不能な Pod が発生してから、ノードが追加されるまでに数分かかることがある。
  • 非効率なノード選択: 複数のノードプールから、必ずしも最適ではないインスタンスタイプを選択することがある。

Karpenter (AWS 製) は、これらの問題を解決する次世代のノードオートスケーラーです。

  • 高速なプロビジョニング: Pod の要件(リソース、 AZ 、アーキテクチャ等)を直接見て、数秒で最適なインスタンスを起動します。
  • コスト効率: Pod に最適なインスタンスタイプ(例: Spot インスタンス)をジャストインタイムで選択するため、無駄なリソースを削減し、コストを大幅に最適化できます。
  • ノードプール不要: CA のように事前に複数のノードプールを定義する必要がなく、運用がシンプルになります。

SRE としての判断: AWS 環境で Kubernetes を運用している場合、新規クラスタでは CA の代わりに Karpenter を第一選択肢として検討すべきです。既存クラスタからの移行も、コスト削減と運用効率化の観点から強く推奨されます。


4. 多層防御セキュリティ

Kubernetes セキュリティは、単一のツールや設定で実現できるものではありません。「Cloud, Cluster, Container, Code」という 4 つのレイヤー (4C’s of Cloud Native Security) を意識した多層防御アプローチが不可欠です。攻撃者が一つの防御層を突破しても、次の層で食い止めることを目的とします。

RBAC: 権限を最小化する原則

RBAC (Role-Based Access Control) は、誰が、何を、どのリソースに対して実行できるかを制御する Kubernetes セキュリティの基盤です。

  • アンチパターン: cluster-admin の乱用
    • 問題: 便宜上、サービスアカウントやユーザーに cluster-admin のような強力な権限を与えてしまうと、一つのコンポーネントが侵害されただけでクラスター全体が乗っ取られるリスクがあります。
    • 対策: 最小権限の原則を徹底します。各コンポーネント(例: CI/CD パイプライン、コントローラー)には、その役割に必要な最小限の権限 (Role / RoleBinding) のみを与えます。 Namespace を跨いだ権限が必要な場合にのみ ClusterRole / ClusterRoleBinding を使用します。
  • 定期的な監査: kubectl-who-can (aquasecurity) や rakkess (raesene) のようなツールを使い、「このサービスアカウントは本当に Secret を list する必要があるのか?」といった観点で定期的に権限を監査し、不要な権限を剥奪するプロセスを確立すべきです。

Pod Security Standards: Pod の実行権限を制限する

Pod Security Standards (PSS) は、 Pod のセキュリティコンテキストを制御し、危険な操作を制限するための仕組みです。 PodSecurityPolicy の後継です。

プロファイル目的ユースケース
Privileged制限なし特殊なシステムコンポーネントのみ
Baseline既知の権限昇格を防ぐほとんどのアプリケーション
Restrictedベストプラクティスを強制本番環境のデフォルトにすべき

Restricted プロファイルでは、以下のような制限が強制されます:

  • root ユーザーでの実行禁止 (runAsNonRoot: true)
  • 権限昇格の禁止 (allowPrivilegeEscalation: false)
  • 特定のカーネル機能 (capabilities) の制限

Namespace ラベルで PSS を適用できます。本番環境の Namespace は restricted を強制し、違反する Pod のデプロイをブロックすることが強く推奨されます。

# restricted な PSS を適用する Namespace
apiVersion: v1
kind: Namespace
metadata:
  name: production-app
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

Network Policies: デフォルト Deny の原則

デフォルトでは、 Kubernetes クラスター内の全ての Pod は相互に通信できます。これはセキュリティ上、非常に危険な状態です。 Network Policy は、 Pod 間の通信を L3/L4 レベルで制御するファイアウォールです。

  • デフォルト Deny から始める: まず、 Namespace 内の全ての通信を拒否するポリシーを適用します。
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: default-deny-all
    spec:
      podSelector: {}
      policyTypes:
      - Ingress
      - Egress
  • 必要な通信のみを許可 (Whitelist): その上で、アプリケーションに必要な通信(例: Frontend Pod から Backend Pod への通信)を個別に許可するポリシーを追加していきます。
  • Cilium の活用: Calico や Cilium といった CNI プラグインは、 L7 レベル(例: GET のみ許可し POST は拒否)の高度なポリシーや、 FQDN ベースの Egress 制御など、標準の Network Policy よりも強力な機能を提供します。

以下の図は、 default-deny ポリシーを適用し、フロントエンドからバックエンドへの通信のみを許可する構成例です。

graph TD
    subgraph "Namespace: my-app"
        direction LR
        Ingress --> FE1(Frontend Pod)
        Ingress --> FE2(Frontend Pod)

        subgraph "Ingress: Allow from Frontend"
            FE1 --> BE1(Backend Pod)
            FE2 --> BE1
        end

        subgraph "Egress: Allow to DB"
            BE1 --> DB[(Database)]
        end
    end

Secrets Management: Vault による集中管理

Kubernetes の Secret オブジェクトは、 Base64 エンコードされているだけで、暗号化されているわけではありません。 etcd に平文で保存されており、 etcd へのアクセス権を持つ者なら誰でも読み取れてしまいます。

  • 外部 Secret ストアの利用: 本番環境では、 HashiCorp Vault や AWS/GCP の Secrets Manager のような外部の Secret 管理ソリューションを利用すべきです。
  • インジェクション方法: External Secrets Operator (ESO) や Secrets Store CSI Driver のようなツールを使い、外部ストアの Secret を Pod に安全にマウント(ファイルとして)または環境変数として注入します。
  • メリット:
    • Secret の集中管理と監査
    • 動的な Secret の生成とローテーション
    • 開発者が本番の Secret を知る必要がなくなる

5. オブザーバビリティとSLO

オブザーバビリティ(可観測性)とは、システムの内部状態を外部の出力からどれだけうまく推測できるか、という性質です。単なるモニタリング(監視)とは異なり、未知の障害(Unknown Unknowns)に対応する能力を意味します。

オブザーバビリティの 3 本柱

  1. メトリクス (Metrics): 一定間隔で収集される数値データ。 Prometheus がデファクトスタンダードです。 SRE の実践では、システムの健康状態を測るために RED メソッド(Rate, Errors, Duration)や USE メソッド(Utilization, Saturation, Errors)が広く使われます。 Prometheus Operator を使うことで、 Prometheus や Alertmanager の管理を宣言的に行うことができます。
  2. ログ (Logs): イベントのタイムスタンプ付きレコード。 JSON のような構造化ログを出力することで、後からの検索や分析が容易になります。 Fluent Bit や Vector のような軽量なエージェントで収集し、 Loki や OpenSearch のような集約基盤に送るのが一般的です。ログの保持期間とコストのバランスが重要な課題となります。
  3. トレース (Traces): マイクロサービス環境におけるリクエストの処理経路を可視化します。 OpenTelemetry が標準的な仕様となりつつあり、 Jaeger や Grafana Tempo といったツールで収集・可視化します。ボトルネックの特定や、サービス間の依存関係の理解に不可欠です。

SLO によるデータ駆動の運用

「システムの可用性は 100% を目指すべき」というのは幻想であり、ビジネス上のコストと見合わないことが多いです。 SLO (Service Level Objective) は、ユーザーの幸福度を測るための現実的な目標設定です。

  • SLI (Service Level Indicator): サービスの特定の側面を測定する指標。例えば、「直近 5 分間の HTTP リクエストのうち、 500ms 以内に 2xx/3xx で応答したものの割合」。
  • SLO (Service Level Objective): SLI が達成すべき目標値。例えば、「月間稼働時間において、 SLI が 99.9% を上回ること」。
  • エラーバジェット (Error Budget): 100% - SLO。 SLO が 99.9% なら、エラーバジェットは 0.1% です。このバジェットの範囲内であれば、意図的な障害(カオスエンジニアリング)や、リスクのある新機能のリリースが許容されます。エラーバジェットは、開発の速度と信頼性のバランスを取るための通貨です。
graph LR
    subgraph "SLO: 99.9%"
        SLI["SLI: 99.95% (現在値)"]
        Budget["エラーバジェット: 0.1% (月間 43 分)"]
    end
    SLI -- "良い状態" --> Budget_OK("バジェット消費: 遅い")
    Budget -- "枯渇すると" --> Freeze("機能リリース凍結")
  • SLO ベースのアラート: 個々のエラーでアラートを出すのではなく、「エラーバジェットの消費ペースが速すぎる(例: 24 時間以内にバジェットを使い切るペース)」場合にのみアラートを出すようにします。これにより、一時的なスパイクによるアラート疲れを防ぎ、本当に重要な問題に集中できます。
  • 宣言的な SLO 管理: SlothPyrra のようなツールを使うと、 SLO を YAML で宣言的に定義し、 Prometheus のレコーディングルールやアラートルールを自動生成できます。

6. 高度なトラブルシューティング

kubectl logsdescribe は基本ですが、複雑な問題の根本原因を特定するには、より高度なテクニックが必要です。

kubectl debug の活用

kubectl debug は、 Pod のトラブルシューティング方法を根本的に変えました。

  • CrashLoopBackOff の Pod を調査する:
    • kubectl logs <pod-name> --previous で、クラッシュする直前のログを確認するのは基本です。
    • それでも原因が分からない場合、 kubectl debug を使って、問題の Pod のコピーを、コマンドを上書きして起動できます。
      # Pod のコピーを sleep 状態で起動し、コンテナイメージや設定を調査
      kubectl debug my-pod --copy-to=my-pod-debug --container=my-app -- sh
  • Ephemeral Container で動作中の Pod にアタッチする:
    • Pod を再起動せずに、デバッグ用のコンテナ(nicolaka/netshoot など、ネットワークツールが満載のイメージ)を注入できます。これにより、本番環境で稼働中の Pod と同じネットワーク名前空間で、 tcpdumpping を実行できます。
      # 動作中の Pod にデバッグコンテナをアタッチ
      kubectl debug -it my-pod --image=nicolaka/netshoot --share-processes -- /bin/bash

ネットワーク問題の切り分け

  1. DNS は引けているか?: Pod 内から nslookup kubernetes.default や、外部サービスのドメイン名を引いてみます。 CoreDNS の Pod のログも確認します。
  2. Pod IP に疎通性はあるか?: ping がなくても、 curl -v telnet://<pod-ip>:<port> で TCP 接続が確立できるか確認できます。
  3. Service 経由でアクセスできるか?: kubectl get endpoints <service-name> で、対象の Pod の IP がエンドポイントに含まれているか確認します。含まれていなければ、 readinessProbe の失敗や、 Pod のラベルと Service のセレクタの不一致が原因です。
  4. Network Policy は邪魔をしていないか?: kubectl debug で注入したコンテナから curl を実行し、通信がタイムアウトする場合、 Egress の Network Policy にブロックされている可能性があります。

ノード障害への対処

  • Node NotReady: kubectl describe node <node-name> で Kubelet の状態を確認します。ノードに SSH ログインし、 systemctl status kubeletjournalctl -u kubelet で kubelet のログを調査します。メモリやディスクの逼迫が原因で Kubelet が不調になることも多いです。
  • crictl によるコンテナランタイムレベルの調査: ノード上で crictl pscrictl logs を使うと、 Docker や containerd と直接対話し、 kubelet が見ているコンテナの状態を正確に把握できます。

SRE のためのツールボックス

  • k9s: ターミナルベースの強力な Kubernetes UI。リソースの探索、ログの表示、 Pod への exec などが高速に行えます。
  • stern: 複数の Pod やコンテナのログを色分けしてストリーミング表示できます。 stern "app-v1-.*" のように正規表現で Pod を指定できるのが強力です。
  • kubectl-flame: kubectl debug を活用し、 Pod で実行中のアプリケーションの CPU プロファイルを Flame Graph として取得します。パフォーマンスのボトルネック特定に非常に有効です。

7. 宣言的クラスタ管理 (GitOps)

Kubernetes の本番運用が成熟してくると、 kubectl apply -f による手動デプロイはスケールしなくなり、ヒューマンエラーの原因となります。GitOpsは、 Git リポジトリを唯一の信頼できる情報源 (Single Source of Truth) とし、クラスタの状態を宣言的に管理するプラクティスです。

GitOps とは何か: Argo CD vs Flux

GitOps を実現する代表的なツールが Argo CD と Flux です。どちらも CNCF の Graduated プロジェクトであり、クラスタ内にエージェントをデプロイし、 Git リポジトリの変更を監視して自動的にクラスタに同期します。

特徴Argo CDFlux
UI洗練された Web UI が標準で付属。可視性が高い。UI は提供されず、 CLI ベース。 Weave GitOps などのサードパーティ UI が必要。
構成Application CRD で同期対象を定義。直感的。Kustomization, HelmRelease など複数の CRD を組み合わせる。柔軟性が高い。
マルチクラスタ標準機能としてマルチクラスタ管理をサポート。単体ではシングルクラスタ。マルチクラスタ管理には追加の構成が必要。
アプローチPull 型。エージェントが Git リポジトリをポーリング。Pull 型。 Argo CD と同様。

SRE としての判断: 視覚的なダッシュボードでデプロイ状況をチーム全体で共有したい、多くのアプリケーションを管理したい場合は Argo CD が適しています。より Kubernetes ネイティブで、 CI/CD パイプラインと密に連携させたい場合は Flux が強力な選択肢となります。

本番環境への安全なプロモーション戦略

GitOps では、環境間のアプリケーションのプロモーション(昇格)も Git 操作で行います。

例: dev -> staging -> production

  1. 開発者はアプリケーションのコードと、 dev 環境用のマニフェスト(例: kustomize/dev )を変更し、 Pull Request を作成します。
  2. CI が成功し、 PR がマージされると、 Argo CD/Flux が自動的に dev クラスタにデプロイします。
  3. staging 環境へのプロモーションは、 staging ブランチへの Pull Request を作成することで行います。この PR では、例えばコンテナイメージのタグを新しいものに更新します。
  4. staging でのテストが完了したら、同様に production ブランチへの PR を作成し、承認を経て本番環境にデプロイされます。

このプロセスにより、全ての変更が Git の履歴として記録され、誰が、いつ、何を、どの環境にデプロイしたかが完全に追跡可能になります。問題が発生した場合も、 Git リポジトリを以前のコミットに戻すだけで、安全にロールバックできます。


8. まとめ

Kubernetes の本番運用は、深い技術的知識と、ビジネス要件や組織の成熟度に合わせたトレードオフの判断が求められる、 SRE の中核的な活動です。

このガイドで解説した主要な原則を再確認します:

  • リソース管理: Linux カーネルの挙動を理解し、 QoS クラスを意識したサイジングを行う。 CPU スロットリングの罠に注意する。
  • 可用性: topologySpreadConstraints で Pod を賢く分散させ、アンチパターンを避けたヘルスチェックを実装し、安全な終了処理を保証する。
  • スケーラビリティ: HPA のチューニングを行い、 Karpenter のような次世代ツールの導入を検討する。
  • セキュリティ: RBAC の定期的な監査、デフォルト Deny の Network Policy 、 restricted な Pod Security Standards を基本とし、 Vault のような専門ツールで Secret を管理する。
  • オブザーバビリティ: SLO を定義し、それを PromQL で計測する。目的に応じたログスタックを選定する。
  • トラブルシューティング: kubectl debug のような高度なツールを使いこなし、根本原因を特定する。
  • クラスタ管理: kubectl による手動操作から脱却し、 GitOps による宣言的で再現可能な管理へと移行する。

これらの原則は一度適用して終わりではありません。アプリケーションの進化、 Kubernetes 自体のアップデート、そしてチームの成長に合わせて、継続的に改善し、洗練させていくことが、堅牢で信頼性の高いシステムを支える鍵となります。


参考リンク

著者について

佐藤 裕介

佐藤 裕介

大規模サービスのインフラ運用経験 10 年以上。 Kubernetes 、 Terraform 、 CI/CD パイプライン構築を得意とし、信頼性の高いシステム基盤を提供します。

サービスに関するお問い合わせ

開発・技術支援に関するご相談はお気軽にお問い合わせください。

お問い合わせ