Skip to content
Published on

HAProxy Ingress Controller — 実績あるLBがKubernetesへ進出

Authors

はじめに

Kubernetesのingressコントローラーを選ぶということは、実質的に「クラスタの正面玄関(north-southトラフィック)を何に任せるか」を決めることです。長らくこの役割の事実上の標準はingress-nginxでしたが、2026年現在、状況はかなり変わりました。

第一に、Kubernetes Ingress API自体が凍結(frozen)されました。もはや新機能は入らず、すべての新規開発はGateway APIへ移行しています。第二に、長らく標準だったingress-nginxはほぼメンテナンスモードに移行し、その過程でアノテーションベースの設定注入に起因するセキュリティ問題(CVE)が繰り返し報告され、運用者の信頼が揺らぎました。

この流れの中で改めて注目される選択肢がHAProxyです。HAProxyはKubernetesが登場するはるか前から、大規模トラフィック環境で実績を積んだL4/L7ロードバランサーです。「すでに20年以上本番で動いてきたデータプレーンを、そのままingressに使う」という点が核心的な魅力です。

ただし落とし穴が一つあります。「HAProxy ingressコントローラー」と呼ばれるプロジェクトが、実は二つあるという点です。一つはHAProxy Technologiesが直接開発する公式プロジェクト、もう一つはJoao Morais氏が長く維持してきたコミュニティプロジェクトです。この二つは名前が似ていますが、設定モデルとアノテーションのprefixがまったく異なります。本記事では二つのプロジェクトを明確に区別しながら、アーキテクチャ、動的reload、TCP/TLS passthrough、レートリミット、Helmデプロイ、Gateway API移行までをコード中心に深く解説します。

Ingressコントローラーの地形 — 2026年の現実

まず全体像を押さえましょう。ingressコントローラーはデータプレーン(実際にトラフィックを処理するプロキシ)とコントロールプレーン(Kubernetes APIをwatchして設定を生成する部分)に分かれます。どのプロキシエンジンを使うかが、性能と機能の性格を決めます。

コントローラーデータプレーンエンジン2026年の状態強み注意点
ingress-nginxNGINX + Luaメンテナンスモード、CVE蓄積圧倒的ユーザー基盤、資料豊富新機能停滞、セキュリティ修正中心
HAProxy (haproxytech公式)HAProxy + Data Plane API活発に開発実績ある安定性、hitless reload、公式サポートアノテーションprefixが別、学習必要
HAProxy (jcmoraisjrコミュニティ)HAProxy活発に開発豊富な機能、embeddedモード単一メンテナー依存
Traefik独自(Go)活発Gateway API先導、動的設定超高負荷ではHAProxy/NGINXに劣る
Envoyベース (Contour, EG)Envoy活発、Gateway API中心gRPC/HTTP2に強い、xDSリソース使用量、複雑さ
CiliumEnvoy + eBPF活発カーネルレベル、Gateway APICNI依存、学習曲線

HAProxy系の位置づけを一文でまとめると、こうなります。「TraefikやEnvoyのように新標準(Gateway API)へ向かいつつ、NGINXのように実績ある高性能データプレーンを保証したいときに選ぶ折衷案」です。

二つのHAProxy ingressプロジェクト — 絶対に混同してはいけない区別

本記事で最初に釘を刺しておくべき部分です。「HAProxy Ingress」を検索すると出てくる二つのプロジェクトは、別々のソフトウェアです。

区分haproxytech/kubernetes-ingressjcmoraisjr/haproxy-ingress
主体HAProxy Technologies(会社公式)Joao Morais(コミュニティ)
リポジトリgithub.com/haproxytech/kubernetes-ingressgithub.com/jcmoraisjr/haproxy-ingress
ドキュメントサイトhaproxy.com/documentation/kubernetes-ingresshaproxy-ingress.github.io
アノテーションprefixhaproxy.orghaproxy-ingress.github.io
設定更新方式Data Plane API + Runtime API中心テンプレートベース + Runtime API
商用サポートHAProxy Enterprise Kubernetes連携コミュニティベース
特徴会社が直接管理、リリース周期明確機能豊富、長い検証実績

両プロジェクトともデータプレーンは同じHAProxyエンジンですが、コントロールプレーンの実装とユーザーインターフェース(アノテーション、ConfigMapキー)が異なります。たとえば同じ「リクエストタイムアウト設定」でも、アノテーションキーが異なります。

公式(haproxytech):     haproxy.org/timeout-client: "30s"
コミュニティ(jcmoraisjr): haproxy-ingress.github.io/timeout-client-fin: "30s"

したがって、インターネットで見つけた例をそのまま貼り付ける前に、必ず「自分が使っているのはどちらのプロジェクトか」を先に確認する必要があります。本記事の本文は特に断りがなければ公式プロジェクト(haproxytech/kubernetes-ingress)を基準とし、コミュニティプロジェクトの差異はその都度指摘します。

選択ガイドをまとめると次のとおりです。

  • 会社レベルの公式サポートと明確なリリースガバナンス、HAProxy Enterpriseへのアップグレード経路が必要なら、公式プロジェクト(haproxytech)が自然です。
  • 長期間蓄積された細かな機能(グローバル設定スニペット、多様な認証オプションなど)とコミュニティの検証実績を重視するなら、jcmoraisjrプロジェクトも強力です。
  • 新規導入で会社方針上ベンダーサポートを受けられるなら、長期的には公式プロジェクトを推奨します。

なぜHAProxyか — データプレーンの強み

HAProxyがingressデータプレーンとして魅力的な理由は、結局のところ「エンジン自体の成熟度」です。

第一に、性能と安定性です。HAProxyは単一プロセスのイベント駆動(マルチスレッドイベントループ)アーキテクチャで、数十万の同時接続を低いCPU/メモリで処理するよう長らく最適化されてきました。コネクションプーリング、keep-alive再利用、バックエンドのヘルスチェックといった基本が堅牢です。

第二に、豊富なL7機能です。ACL(アクセス制御リスト)ベースの精緻なルーティング、ヘッダー操作、レートリミット、スティックテーブル(stick-table)ベースの状態追跡、サーキットブレーカー類似の動作(バックエンド遮断)などを、設定だけで実装できます。

第三に、可観測性です。HAProxyのstatsページとPrometheus exporterは、バックエンドごとの応答時間、キュー長、接続状態を非常に詳細に提供します。

第四に、動的reloadです。後ほど詳しく扱いますが、HAProxyはRuntime APIとhitless reload(seamless reload)により、設定変更時に既存接続を切らずに反映できます。ingressのようにバックエンド(Pod)が頻繁に変わる環境では、これが決定的な利点です。

アーキテクチャ — コントローラーはHAProxyをどう運転するか

公式コントローラーの内部構造は、次のような流れで動作します。

+-----------------------------------------------------------------------+
|  HAProxy Ingress Controller Pod                                       |
|                                                                       |
|   +-------------------------+        +----------------------------+   |
|   |  Controller (Go)        |        |  HAProxy プロセス             |   |
|   |                         |        |                            |   |
|   |  - K8s API watch        | -----> |  - frontend (80/443)       |   |
|   |    Ingress / Service /  |  Data  |  - backend (Pod IP:port)   |   |
|   |    Endpoints / Secret / |  Plane |  - ACL / map / stick-table |   |
|   |    ConfigMap            |  API   |  - stats / runtime socket  |   |
|   |  - 設定生成/検証          | <----- |                            |   |
|   |  - Runtime API 呼び出し   |  state |                            |   |
|   +-------------------------+        +----------------------------+   |
+-----------------------------------------------------------------------+
            ^                                       |
            | watch (informer)                      | forward
            |                                       v
   +------------------+                  +-------------------------+
   | Kubernetes API   |                  |  バックエンドPods (Service) |
   |  Server          |                  |  app-a / app-b / ...    |
   +------------------+                  +-------------------------+

        クライアント ──HTTP/HTTPS/TCP──> [frontend] ──ACLルーティング──> [backend] ──> Pod

要点は、コントローラー(Goプロセス)とデータプレーン(HAProxyプロセス)が同じPod内で分離されている点です。コントローラーはKubernetes APIをinformerでwatchし、Ingress/Service/Endpoints/Secret/ConfigMapの変化を検知すると、次の二つのいずれかでHAProxyに反映します。

  • エンドポイント(Pod IP)のような頻繁な変化は、Runtime APIを通じてreloadなしで即座にサーバーを追加/削除します。
  • frontendの追加やACL構造の変更のような構造的変化は、設定を再生成しhitless reloadをトリガーします。

この「reloadが必要な変更」と「reloadが不要な変更」を区別することが、HAProxy ingressをうまく運用する核心概念です。

設定の三つのレイヤー — Global / Defaults / Backend

HAProxy ingressの設定は、適用範囲に応じて三つのレイヤーに分かれます。この構造を理解すれば、「この設定をConfigMapに入れるべきか、アノテーションに入れるべきか」が明確になります。

レイヤー適用対象設定場所
GlobalHAProxyプロセス全体ConfigMap(コントローラー全体)maxconn, ssl既定値, nbthread
Defaultsすべてのfrontend/backendの既定値ConfigMap既定タイムアウト, ログフォーマット
Ingress/Service個別のルーティング単位IngressまたはServiceアノテーションpathルーティング, レートリミット, バックエンドプロトコル

この優先順位のおかげで、「全体に設定しつつ特定サービスだけ別にする」パターンが自然に実現できます。たとえば既定タイムアウトはConfigMapで30秒にしつつ、アップロードAPIだけアノテーションで300秒を与えられます。

デプロイ実習 — Helmで公式コントローラーをインストール

それでは実際にインストールしてみましょう。公式プロジェクトはHelmチャートを提供しています。基準環境はKubernetes v1.32系です。

まずリポジトリを追加します。

helm repo add haproxytech https://haproxytech.github.io/helm-charts
helm repo update
helm search repo haproxytech/kubernetes-ingress

最もシンプルなインストールは次のとおりです。

kubectl create namespace haproxy-controller

helm install haproxy-ingress haproxytech/kubernetes-ingress \
  --namespace haproxy-controller \
  --set controller.kind=Deployment \
  --set controller.replicaCount=2

本番ではvaluesファイルで明示的に管理するのがよいです。次は実務でよく使うvaluesの例です。

controller:
  kind: Deployment
  replicaCount: 2

  # ノードのホストネットワークではなくLoadBalancerタイプのServiceを使用
  service:
    type: LoadBalancer
    externalTrafficPolicy: Local   # クライアントのソースIPを保持

  # IngressClass設定 — 複数コントローラー共存時は必須
  ingressClass: haproxy
  ingressClassResource:
    enabled: true
    default: false

  # HAProxyのglobal/default設定を注入するConfigMap値
  config:
    timeout-client: "30s"
    timeout-server: "30s"
    timeout-connect: "5s"
    maxconn: "100000"
    ssl-redirect: "true"

  # リソースのrequests/limits
  resources:
    requests:
      cpu: 500m
      memory: 256Mi
    limits:
      cpu: "2"
      memory: 512Mi

  # Prometheusメトリクスの公開
  serviceMonitor:
    enabled: true

  # Podの分散配置
  podDisruptionBudget:
    enable: true
    minAvailable: 1
helm upgrade --install haproxy-ingress haproxytech/kubernetes-ingress \
  --namespace haproxy-controller \
  -f values-prod.yaml

インストール後の確認は次のとおりです。

kubectl -n haproxy-controller get pods
kubectl -n haproxy-controller get svc
kubectl get ingressclass

ここでexternalTrafficPolicyをLocalにする理由を一度押さえておきましょう。既定値のClusterはノード間SNATを経由するため、クライアントの本当のIPを失います。Localにすると受け取ったノードでのみ処理され、ソースIPが保持されます。レートリミットやIPベースのACLを使うなら、このIP保持は実質的に前提条件です。

基本ルーティング — Ingressリソースの作成

最も基本的なホスト/パスベースのルーティング例です。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ingress
  namespace: demo
  annotations:
    haproxy.org/load-balance: "roundrobin"
spec:
  ingressClassName: haproxy
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-frontend
                port:
                  number: 80
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-backend
                port:
                  number: 8080

ingressClassNameを必ず明示する点に注意してください。複数のingressコントローラーが共存するクラスタでこれを省くと、どのコントローラーも処理しないか、意図しないコントローラーが拾ってしまいます。

アノテーション — 実務でよく使うもの

公式プロジェクトのアノテーションprefixはhaproxy.orgです。よく使うものをまとめると次のとおりです。

アノテーション意味例の値
haproxy.org/load-balanceロードバランシングアルゴリズムroundrobin, leastconn
haproxy.org/timeout-clientクライアントタイムアウト30s
haproxy.org/timeout-serverバックエンド応答タイムアウト60s
haproxy.org/ssl-redirectHTTPをHTTPSへリダイレクト"true"
haproxy.org/server-protoバックエンドプロトコルh2 (HTTP/2)
haproxy.org/path-rewriteパス書き換え/api/(.*) から /v1/
haproxy.org/rate-limit-requestsリクエストのレートリミット"100"
haproxy.org/whitelist許可IP帯域10.0.0.0/8

たとえばバックエンドとHTTP/2で通信し、応答タイムアウトを延ばすには次のように書きます。

metadata:
  annotations:
    haproxy.org/server-proto: "h2"
    haproxy.org/timeout-server: "120s"
    haproxy.org/check: "enabled"

ここでコミュニティプロジェクト(jcmoraisjr)との違いを改めて強調します。同じ意図でもprefixとキー名が異なります。

公式(haproxytech):     haproxy.org/server-proto: "h2"
コミュニティ(jcmoraisjr): haproxy-ingress.github.io/backend-protocol: "h2"

動的reload — Runtime APIとhitless reload

この節がHAProxy ingressの本当の差別化点です。ingress環境ではPodがスケールイン/アウトし、ローリングアップデートでIPが常に変わります。この変化をデータプレーンに反映するたびにプロキシを完全に再起動すると、接続が切れます。

HAProxyは二つのメカニズムでこの問題を解決します。

第一に、Runtime APIです。HAProxyはUnixソケットを通じたランタイムコマンドを受け取ります。コントローラーはバックエンドサーバーの追加/削除/重み変更といった動作を、reloadなしで即座に適用します。

[Podのスケールアウト発生]
   Endpoints変更を検知
   コントローラーがRuntime APIでコマンド送信:
     set server backend_app/srv3 addr 10.244.2.17 port 8080
     set server backend_app/srv3 state ready
   HAProxyがreloadなしで新規サーバーを即座に有効化
   (既存接続への影響なし)

第二に、hitless reload(seamless reload)です。frontendの追加やACL構造の変更のように設定ファイル自体を変えなければならないときはreloadが避けられませんが、HAProxyはこのreloadを無停止で行います。新しい設定で新プロセスを起動しつつ、既存プロセスは処理中の接続が終わるまで維持し、リスニングソケットは両プロセスで共有します。SO_REUSEPORTとソケットハンドオーバーの仕組みのおかげで、reloadの瞬間でも新規接続が拒否されません。

運用上の重要な含意はこうです。「エンドポイント変更はreloadを起こさないので安心してよいが、頻繁なConfigMap/構造変更はreloadを誘発しうる」という点です。reload頻度が高すぎると、処理中の接続を終えられない旧プロセスが蓄積しうるため、hard-stop-afterのような値で旧プロセスの最大生存時間を制御します。

controller:
  config:
    hard-stop-after: "30m"   # reload後の旧プロセス最大生存時間

TCPモードとTLS passthrough

HTTPだけ処理するならingressで十分ですが、データベースやメッセージブローカーのようにTCPをそのまま公開する必要がある場合があります。HAProxy ingressはこれを二つの方法で支援します。

第一に、TCPサービスです。公式コントローラーは別のConfigMapで任意ポートのTCPをバックエンドサービスへ接続します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: haproxy-kubernetes-ingress-tcp
  namespace: haproxy-controller
data:
  # 形式: "フロントポート": "namespace/service:port"
  "5432": "demo/postgresql:5432"
  "6379": "demo/redis:6379"

こうするとコントローラーが5432、6379ポートにTCP frontendを作り、各サービスへ転送します。ただし、LoadBalancer Serviceまたはホストポートでこれらのポートが外部に開いている必要があります。

第二に、TLS passthroughです。通常のingressはコントローラーでTLSを終端(termination)しますが、バックエンドが直接TLSを処理する必要がある場合(例: mTLSをバックエンドまで通す)は、コントローラーが復号せずそのまま流す必要があります。このときはSNI(Server Name Indication)を見てルーティングします。

TLS termination (既定):
  クライアント ──TLS──> [HAProxy: 復号] ──平文/再暗号化──> バックエンド

TLS passthrough:
  クライアント ──TLS──> [HAProxy: SNIのみ読み復号しない] ──TLSそのまま──> バックエンド
                            (証明書はバックエンドにある)

passthroughはTCPモードの一種として動作します。コントローラーがSSLハンドシェイクのSNIフィールドをACLで検査し、ホストごとに別のバックエンドへ送ります。ただしこの場合コントローラーはHTTPヘッダーを見られないため、pathベースのルーティングやヘッダー操作は不可能です。ホスト(SNI)単位のルーティングのみ可能という制約を覚えておく必要があります。

レートリミットとACL — スティックテーブルの力

HAProxyの本当の強みの一つが、スティックテーブル(stick-table)を用いた状態追跡です。スティックテーブルはメモリ上のキーバリューストアで、ソースIPごとのリクエスト回数、接続数、エラー率などを追跡できます。これをベースにレートリミットとアビューズ遮断を実装します。

アノテーションベースの基本的なレートリミットは次のとおりです。

metadata:
  annotations:
    haproxy.org/rate-limit-requests: "100"
    haproxy.org/rate-limit-period: "1m"
    haproxy.org/rate-limit-status-code: "429"

この設定はソースIPあたり1分に100リクエストを超えると429を返します。このとき先に強調したexternalTrafficPolicy: Localで本当のクライアントIPが保持されていてはじめて意味があります。

ACLはより細かな制御を可能にします。特定のパスを特定のIP帯域にだけ許可したり、特定のヘッダーを持つリクエストだけを通したりといった具合です。公式コントローラーではwhitelist/blacklistアノテーションと、グローバル設定スニペットでrawなHAProxy設定を注入できます。

metadata:
  annotations:
    # 管理者パスは社内帯域からのみアクセス許可
    haproxy.org/whitelist: "10.0.0.0/8,192.168.0.0/16"

内部で生成されるHAProxy設定の断片を概念的に見ると次のようになります。(これはコントローラーが自動生成するもので、手で書くものではありません。)

frontend https
    bind *:443 ssl crt /etc/haproxy/certs/

    # スティックテーブル: ソースIPごとのリクエストカウントを追跡
    stick-table type ip size 100k expire 1m store http_req_rate(1m)
    http-request track-sc0 src

    # 分間100超で429
    acl too_many sc_http_req_rate(0) gt 100
    http-request deny deny_status 429 if too_many

    # 社内ネットワークのみ許可するACL
    acl internal_net src 10.0.0.0/8 192.168.0.0/16
    acl admin_path path_beg /admin
    http-request deny if admin_path !internal_net

    use_backend api_backend if { path_beg /api }
    default_backend web_backend

この水準のトラフィック制御をアノテーションとConfigMapだけで表現できるのが、HAProxyデータプレーンの力です。

TLSとcert-manager連携

実務では証明書はcert-managerで自動発行/更新するのが標準です。HAProxy ingressも標準のIngress TLSスペックに従うため、cert-managerと自然に連携します。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ingress-tls
  namespace: demo
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    haproxy.org/ssl-redirect: "true"
spec:
  ingressClassName: haproxy
  tls:
    - hosts:
        - app.example.com
      secretName: app-example-com-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-frontend
                port:
                  number: 80

cert-managerがACME(Let's Encrypt)チャレンジを通過して証明書をSecretとして作ると、コントローラーがそのSecretをwatchしてHAProxyに適用します。証明書更新時はRuntime APIで適用されるため、通常はreloadなしで反映されます。

Gateway APIへの流れ — Ingressの次の標準

冒頭で触れたように、Ingress APIは凍結され、後続標準はGateway APIです。Gateway APIは、ingressの限界(アノテーションの乱立、役割分離の欠如、L4の貧弱さ)を正面から解決するために設計されました。

核心的な違いは役割ベースのリソース分離です。

IngressモデルGateway APIモデル担当役割
IngressClassGatewayClassインフラ/プラットフォームチーム
(該当なし)Gateway (リスナー, ポート, TLS)クラスタ運用者
IngressルールHTTPRoute / TCPRoute / GRPCRouteアプリ開発チーム
[Gateway APIトラフィックフロー]

  GatewayClass (コントローラー = haproxy)
        │ 実装
  Gateway ──> リスナー定義 (例: 443/HTTPS, TLS証明書)
        │ 参照
  HTTPRoute ──> ホスト/パスマッチング ──> backendRefs (Service)
   クライアント ──> [Gateway:HAProxy frontend] ──> [HTTPRouteルール] ──> Service

HAProxy公式コントローラーもGateway APIサポートを段階的に追加してきており、HTTPRouteやTCPRouteといった主要リソースを扱えます。実務で推奨する戦略は次のとおりです。

  • すでにうまく動いているIngressリソースを今すぐ移行する必要はありません。Ingress APIが消えるわけではなく、ただ新機能が入らないだけです。
  • 新規ワークロード、特にL4(TCP/UDP)ルーティングやトラフィック分割(canary)のような高度な機能が必要な場合は、Gateway APIを優先的に検討します。
  • コントローラーのGateway APIサポート成熟度(どのRouteタイプ、どのフィルタを支援するか)を、導入前に必ずドキュメントで確認します。

シンプルなHTTPRouteの例は次のとおりです。

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: haproxy-gateway
  namespace: demo
spec:
  gatewayClassName: haproxy
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - name: app-example-com-tls
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web-route
  namespace: demo
spec:
  parentRefs:
    - name: haproxy-gateway
  hostnames:
    - "app.example.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: api-backend
          port: 8080
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: web-frontend
          port: 80

運用とチューニング — 可観測性、HA、性能

可観測性

HAProxyのstatsとPrometheusメトリクスはトラブルシューティングの出発点です。ServiceMonitorをオンにすると、バックエンドごとの応答時間、アクティブ接続数、キュー滞留、reload回数などを収集できます。特に次の指標をダッシュボードに載せておくことを推奨します。

  • バックエンドごとの5xx比率と平均応答時間
  • キューに待機中のリクエスト数(バックエンド飽和のシグナル)
  • reload頻度(設定変動が頻繁すぎないか診断)
  • アクティブ/アイドル接続数とmaxconnに対する使用率

高可用性構成

コントローラーは最低2レプリカ以上で運用し、PodDisruptionBudgetで同時終了を制限します。ノード障害に備え、topologySpreadConstraintsやanti-affinityで異なるノード/ゾーンに分散します。外部からの入口はLoadBalancer Service(クラウド)またはMetalLB(オンプレ)で公開し、その前段のクラウドLBがコントローラーレプリカへトラフィックを分散します。

            外部クライアント
   [クラウドLB / MetalLB VIP]
         │              │
         ▼              ▼
   [HAProxy Pod 1]  [HAProxy Pod 2]   (異なるノード/ゾーン)
         │              │
         └──────┬───────┘
         バックエンドサービスPods

性能チューニングのポイント

  • nbthreadをノードのコア数に合わせて設定し、マルチコアを活用します。
  • maxconnをワークロードに合わせて十分に取りつつ、メモリとファイルディスクリプタの上限も併せて考慮します。
  • keep-aliveとバックエンドコネクション再利用をオンにし、ハンドシェイクのオーバーヘッドを減らします。
  • バックエンドがHTTP/2やgRPCなら、server-protoで明示してプロトコルのダウングレードを防ぎます。
controller:
  config:
    nbthread: "4"
    maxconn: "100000"
    timeout-http-keep-alive: "60s"

落とし穴とトラブルシューティング

実務で頻繁に遭遇する問題とその原因を整理します。

第一に、二つのプロジェクトのアノテーション混用です。インターネットの例をそのまま貼ったのに設定が効かないなら、十中八九prefixが別プロジェクトのものです。haproxy.orgとhaproxy-ingress.github.ioを混同しないでください。

第二に、ingressClassNameの欠落です。複数コントローラーがあるクラスタでクラスを指定しないと、ルーティングが欠落するか衝突します。各IngressにingressClassNameを明示することを標準にしてください。

第三に、クライアントIPの喪失です。レートリミットやIP ACLがすべてのトラフィックを一つのIP(ノードIP)として認識するなら、externalTrafficPolicyがClusterであるか、前段のLBがPROXYプロトコル/X-Forwarded-Forを正しく伝えていません。ソースIP保持の経路を最後まで点検してください。

第四に、reloadの暴走です。メトリクスでreload頻度が異常に高いなら、設定やConfigMapが自動化によって頻繁に変更されている可能性があります。エンドポイント変更はreloadを起こさないのが正常なので、reloadが頻繁なら構造的変更の原因を追跡します。

第五に、TLS passthroughでのpathルーティング試行です。passthroughはコントローラーが復号しないため、HTTP pathを見られません。pathベースのルーティングが必要ならtermination モードを使う必要があります。

第六に、バックエンドのヘルスチェック未設定によるトラフィックのブラックホールです。checkをオンにしないと、死んだPodへもトラフィックが行きかねません。アノテーションでヘルスチェックを明示的にオンにし、readinessProbeとの一貫性を確認してください。

診断の出発点は常にコントローラーPod内のHAProxy設定とログです。

# コントローラーのログ確認
kubectl -n haproxy-controller logs deploy/haproxy-ingress -c haproxy-ingress

# コントローラーPodに入り生成された設定を確認
kubectl -n haproxy-controller exec -it deploy/haproxy-ingress -- cat /etc/haproxy/haproxy.cfg

# Runtimeソケットで現在のサーバー状態を照会 (Pod内部)
echo "show servers state" | socat stdio /var/run/haproxy-runtime-api.sock

おわりに

HAProxy ingressコントローラーの最大の価値は、「実績あるデータプレーンをKubernetesの入口にそのまま持ち込む」点にあります。hitless reloadとRuntime APIのおかげで、Podが頻繁に変わる環境でも接続を切らずに安定してトラフィックを流せ、スティックテーブルベースのレートリミットとACLは別途のWAFなしでも相当な水準のトラフィック制御を提供します。

導入時にまず決めるべきは、「公式(haproxytech)プロジェクトか、コミュニティ(jcmoraisjr)プロジェクトか」です。二つのプロジェクトはアノテーションモデルが異なるため、この選択を明確にしたうえで一貫して設定を書く必要があります。新規導入でベンダーサポートが必要なら公式プロジェクトを、長いコミュニティ検証と豊富な機能を望むならjcmoraisjrを検討してください。

最後に、2026年の全体像を忘れないでください。Ingress APIは凍結され、未来はGateway APIです。今うまく動いているIngressは維持しつつ、新規ワークロードと高度なルーティングはGateway APIで設計する二重戦略が現実的です。HAProxyはこの移行期に「安定性をそのまま持ちつつ新標準へ移れる」頼もしい選択肢です。

参考資料