Skip to content
Published on

Ingress Controller の高可用性とスケーリング

Authors

はじめに

Ingress コントローラはクラスタの単一の入り口です。入り口が死ぬと、その背後のすべてのサービスがどれだけ健全でも、外部からは全面障害に見えます。だからこそコントローラの高可用性(HA)は「あれば良いもの」ではなく、運用の前提条件です。

HA は単に「replica を2つに増やした」ことではありません。コントローラが reload されるとき進行中のコネクションが切れてはならず、Pod が終了するときトラフィックが graceful に抜けねばならず、ノードや AZ が丸ごと消えてもトラフィックが流れ続けねばなりません。さらにトラフィックが増えれば自動的に拡張し、ingress オブジェクトが数千に膨らんでもコントローラが耐えねばなりません。

本記事では ingress-nginx を基準に、HA トポロジの選択、公開方式別の可用性、graceful drain と無停止デプロイ、reload の影響最小化、leader election、マルチ AZ 分散、大規模 ingress 環境の性能、リソースサイジング、負荷テスト手法、障害シナリオまでを運用の観点で扱います。2026年現在、Ingress API は frozen であり Gateway API が後継標準なので、HA 設計が Gateway API へどう引き継がれるかも最後に触れます。

HA トポロジ: DaemonSet vs Deployment + HPA

コントローラをどう配置するかが最初の分かれ道です。主流のパターンは2つあります。

区分Deployment + HPADaemonSet
配置任意のノードに replica N 個全(あるいはラベル付き)ノードに1個ずつ
スケーリングHPA で replica を自動調整ノード数に従属
公開との相性cloud LoadBalancer ServicehostNetwork/hostPort
適した環境大半のクラウドベアメタル、エッジ、ノード=容量
リソース効率負荷に応じて弾力的ノードごとに固定コスト

クラウド環境のデフォルトの選択は Deployment + HPA です。cloud LoadBalancer がトラフィックを受けてノードへ分散し、コントローラ replica は負荷に応じて増減します。ベアメタルやエッジのようにノード自体がトラフィックの入り口である環境では、DaemonSet + hostNetwork が一般的です。各ノードの 80/443 をコントローラが直接占有し、1ホップを削る方式です。

Deployment ベースの基本的な HA 構成の例です。

controller:
  kind: Deployment
  replicaCount: 3
  minAvailable: 2
  podDisruptionBudget:
    enabled: true
    minAvailable: 2
  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: topology.kubernetes.io/zone
      whenUnsatisfiable: DoNotSchedule
      labelSelector:
        matchLabels:
          app.kubernetes.io/name: ingress-nginx
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - topologyKey: kubernetes.io/hostname
          labelSelector:
            matchLabels:
              app.kubernetes.io/name: ingress-nginx

ここで重要なのは3点です。(1) PodDisruptionBudget で同時に落ちる Pod 数を制限し、ノードメンテナンス中でも最小可用性を保証します。(2) podAntiAffinity で replica を異なるノードに分散します。(3) topologySpreadConstraints で AZ 間にも均等に広げます。

公開方式別の HA

コントローラが外部トラフィックを受ける方式によって HA の特性が変わります。

方式 A: cloud LoadBalancer Service
[インターネット] → [クラウド LB] → [NodePort] → [コントローラ Pod]
  - LB がヘルスチェックで死んだノードを自動除外
  - externalTrafficPolicy: Local でクライアント IP 保存 + 余分なホップ削減

方式 B: hostNetwork (DaemonSet)
[インターネット] → [ノード IP:80/443] → [コントローラ(ホストネットワーク)]
  - 1ホップ削減、最低レイテンシ
  - ノードあたりコントローラ1個の制約、ポート衝突に注意

方式 C: NodePort + 外部 LB(自前運用)
[インターネット] → [外部 LB] → [NodeIP:NodePort] → [コントローラ]
  - ベアメタルで MetalLB などと組み合わせ

方式 A の externalTrafficPolicy は重要な選択です。Cluster(デフォルト)は全ノードがトラフィックを受けて内部で再分散するため分散は均等ですが、クライアント IP が隠れ、1ホップ増えます。Local は該当ノードの Pod へのみ送るため IP を保存しホップを削りますが、Pod がないノードは LB ヘルスチェックから外れるため、分散がノードごとの Pod 数に依存します。HA の観点では、Local のときノードごとのコントローラ Pod 分布を均衡に保つことが重要です。

graceful drain と無停止デプロイ

コントローラ Pod が終了するとき、進行中だったリクエストが切れるとユーザーは 502 を見ます。無停止の鍵は「エンドポイントから外れること」と「実際の終了」の間に十分な猶予を置くことです。

終了シーケンスは次のように流れるべきです。

1. Pod に SIGTERM が届く直前、kube が Endpoints から Pod の削除を開始
2. preStop フック: sleep で LB/kube-proxy が削除を伝播する時間を確保
3. nginx が graceful shutdown を開始(新規コネクション拒否、既存を完了)
4. terminationGracePeriodSeconds 内に in-flight リクエストを完了
5. プロセス終了

ingress-nginx での設定例です。

controller:
  terminationGracePeriodSeconds: 300
  lifecycle:
    preStop:
      exec:
        command:
          - /bin/sh
          - -c
          - sleep 30; /wait-shutdown

preStop の sleep が必要な理由は、Endpoints の削除がすべての LB と kube-proxy に伝播するまで時間がかかるからです。この猶予なしに即終了すると、まだこの Pod へトラフィックを送る経路が残っており、コネクションが切れます。terminationGracePeriodSeconds は、最も長い正常なリクエスト(例: 大容量アップロード、ストリーミング)が完了できるだけ十分でなければなりません。

デプロイ戦略は RollingUpdate に maxSurge を設けて、常に十分な replica が生きているようにします。

controller:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0

maxUnavailable: 0 は新しい Pod が Ready になる前に既存の Pod を落とさないようにし、デプロイ中の容量低下を防ぎます。

reload の影響最小化

ingress-nginx の特徴は、Ingress リソースが変わるたびに nginx 設定を再読み込みすることです。reload は基本的に graceful ですが(既存 worker がコネクションを完了し、新しい worker が新規コネクションを受ける)、頻繁な reload はメモリ使用量の変動、long-lived コネクション(WebSocket、gRPC ストリーム)の切断、瞬間的なレイテンシスパイクを引き起こすことがあります。

reload の衝撃を減らす方法です。

# ConfigMap
data:
  # 動的な endpoint 変更は reload なしで Lua により反映(デフォルト有効)
  # worker shutdown の猶予を増やし in-flight をより保存
  worker-shutdown-timeout: "240s"
  # reload 直後の負荷を緩和する keepalive チューニング
  upstream-keepalive-connections: "320"
  upstream-keepalive-timeout: "60"

核心的な洞察は、ingress-nginx が エンドポイント(Pod IP)の変更を reload なしで処理する ことです。Pod のスケールや再起動といったよくあるイベントは Lua ベースの動的構成で吸収されるため reload を引き起こしません。reload を呼ぶのは主に Ingress スペック・アノテーション・証明書の変更です。したがって reload 頻度を減らすには、頻繁な Ingress 変更(例: 自動化ツールが毎デプロイでアノテーションをトグルする)を点検するのが効果的です。

運用中は reload 頻度をメトリクス(config_last_reload_success_timestamp の changes)で監視し、WebSocket/gRPC のような long-lived トラフィックが多いなら worker-shutdown-timeout を十分に取って reload 時の切断を最小化します。

leader election

ingress-nginx の複数の replica はすべてトラフィックを処理しますが、一部の作業(特に Ingress の status.loadBalancer フィールドの更新)は一つのインスタンスだけが行うべきです。これに leader election を使います。

リーダーは Lease オブジェクトを通じて選出され、リーダーのみが Ingress オブジェクトの状態を更新します。データプレーン(実際のトラフィック処理)は全 replica が等しく担うため、リーダーが死んでもトラフィックは切れず、新しいリーダーが選出されるだけです。運用者が知っておくべき点は、leader election が正常に動作するには RBAC に Lease リソースへの権限が必要で、election 関連の警告ログが繰り返される場合は権限や API サーバ接続を点検すべきということです。

[replica A] ──┐
[replica B] ──┼─→ すべてデータプレーン(トラフィック)を処理
[replica C] ──┘
     └─ Lease を通じて1人だけがリーダー → Ingress status 更新を担当
        リーダー死亡時 → 自動的に再選出、トラフィックへの影響なし

マルチ AZ 分散

単一 AZ にコントローラが集中していると、その AZ 障害時に全面ダウンです。前述の topologySpreadConstraints が AZ 分散の鍵となるツールです。

controller:
  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: topology.kubernetes.io/zone
      whenUnsatisfiable: DoNotSchedule
      labelSelector:
        matchLabels:
          app.kubernetes.io/name: ingress-nginx
    - maxSkew: 1
      topologyKey: kubernetes.io/hostname
      whenUnsatisfiable: ScheduleAnyway
      labelSelector:
        matchLabels:
          app.kubernetes.io/name: ingress-nginx

zone 単位は DoNotSchedule で厳密に分散し、hostname 単位は ScheduleAnyway でできる限り分散しつつスケジューリングを妨げない組み合わせが実用的です。クラウド LB は通常マルチ AZ を認識し、生きている AZ にのみトラフィックを送るため、コントローラが全 AZ に均等にあれば、一つの AZ が死んでも残りでサービスが継続します。replica 数は「一つの AZ が丸ごと抜けても残りの AZ がピークトラフィックを賄える」ように取るのが原則です(N+1 以上)。

大規模 ingress オブジェクトの性能

ingress オブジェクトが数十個のときと数千個のときでは、コントローラの動作特性が異なります。オブジェクトが増えると次が問題になります。

  • 生成される nginx 設定ファイルが巨大化し reload 時間が延びる
  • コントローラのメモリ/CPU 使用量が増加する
  • API サーバの watch 負荷が増加する
  • reload 1回のコストが大きくなり、変更頻度がそのまま負担に直結する

緩和戦略は次のとおりです。第一に、ネームスペースやドメイングループごとに コントローラをシャーディング します。複数の IngressClass とコントローラインスタンスを置き、各コントローラが一部の Ingress のみを watch するようにすれば、設定サイズと reload コストが分散します。

# シャード A コントローラ
controller:
  ingressClassResource:
    name: nginx-shard-a
    controllerValue: "k8s.io/ingress-nginx-shard-a"
  watchNamespaces: "team-a,team-b"

第二に、不要なアノテーション変更を減らして reload 頻度を下げます。第三に、メトリクスで reload 所要時間を監視し、閾値を超えたらシャーディングを増やします。大規模環境では単一の巨大コントローラよりも複数の小さなシャードのほうが運用的に安定する場合が多いです。

リソースサイジングと負荷テスト

適切な requests/limits なしに HA は崩れます。CPU 限界に達してコントローラが throttle されると、ヘルスチェック失敗 → 再起動 → トラフィックの揺れの悪循環が生じます。

サイジングの出発点は測定です。負荷テストで「リクエストあたりの CPU コスト」と「同時コネクションあたりのメモリ」を求めたうえで、ピークトラフィックにヘッドルームを加えて算定します。

# 簡単な負荷生成(vegeta)
echo "GET https://app.example.com/" | \
  vegeta attack -rate=2000 -duration=300s | \
  vegeta report

# あるいは k6 で段階的な負荷増加
k6 run --vus 500 --duration 5m loadtest.js

負荷テスト中に観察する指標: コントローラの CPU/メモリ、active connections、p99 レイテンシ、5xx 比率、そして意図的に Pod を殺したとき(chaos)の回復時間です。特に reload を負荷中に起こしてみて(例: Ingress を繰り返し修正)、long-lived コネクションが切れるか、レイテンシスパイクがどれほど大きいかを測定して初めて、実運用の挙動が分かります。

サイジングのガイドラインは環境ごとに異なりますが、requests を低くしすぎてノードが過密なときにコントローラがリソースを得られない状況を避け、limits は reload 時の瞬間的なメモリ増加を吸収できる余裕を持たせるのが安全です。

障害シナリオと対応

HA 設計とは「何が死にうるか」を事前に描くことです。

障害影響HA 設計が防ぐ方法
コントローラ Pod 1個クラッシュ一部の容量損失replica N 個 + PDB で残容量を維持
ノード1個ダウンそのノードの Pod 損失podAntiAffinity でノード分散
AZ 1個ダウン該当 AZ 全体の損失topologySpread + N+1 サイジング
トラフィック急増飽和、レイテンシ増加HPA 自動スケール + ヘッドルーム
reload 暴走long-lived 切断、レイテンシworker-shutdown-timeout、変更頻度管理
ingress オブジェクト爆増reload 遅延シャーディング
証明書期限切れTLS 全面失敗cert-manager 自動更新 + 期限アラート

各シナリオに対して「検知(メトリクス/アラート)→ 自動緩和(HPA/再スケジュール)→ 手動介入が必要な時点」を事前に定めておけば、実際の障害時に慌てず流れます。特に AZ 障害とトラフィック急増はしばしば一緒に来る(一つの AZ が抜けると残りの AZ に負荷が集中する)ため、N+1 容量と HPA が連携して動作することを検証すべきです。

Gateway API 時代の HA

2026年現在、Ingress API は frozen であり Gateway API が後継標準です。HA の原理は大半が引き継がれますが、いくつかが改善されます。Gateway API 実装(Envoy ベースの Contour/Istio、Cilium、NGINX Gateway Fabric など)は、コントロールプレーンとデータプレーンがより明確に分離されており、設定変更がデータプレーンの reload に直結しない場合が多いです。Envoy ベースは xDS で設定を動的にプッシュするため、ingress-nginx 式の「ファイル reload」の衝撃が減ります。

また Gateway API の3層モデル(GatewayClass/Gateway/HTTPRoute)は責任分離が明確で、一つの Gateway に Route が集中しすぎる問題を複数の Gateway へ自然に分割できます。これは前述の「シャーディング」を API のレベルでより優雅に表現します。いま ingress-nginx で PDB・antiAffinity・topologySpread・graceful drain をきちんと整えておけば、Gateway API へ移行する際にこれらの運用パターンはほぼそのまま有効です。

おわりに

Ingress コントローラ HA の核心は「単一の入り口を単一障害点にしないこと」です。replica の多重化と PDB で容量を守り、antiAffinity と topologySpread でノード・AZ に分散し、graceful drain と無停止デプロイで変更中もトラフィックを切らず、HPA で負荷に適応し、シャーディングで大規模を賄います。

これらの設定は一度で完成しません。負荷テストと意図的な障害注入(chaos)で仮定を検証し、メトリクスで実際の挙動を観察しながら段階的に磨いていくのが HA 運用の本質です。入り口が揺らがないときに初めて、その背後のすべてのサービスが安心して動けるのです。

参考資料