Skip to content
Published on

Contour 完全ガイド - Envoy ベースの Ingress と HTTPProxy で Kubernetes トラフィックを扱う

Authors

はじめに

Kubernetes で外部トラフィックをクラスター内部のサービスへルーティングするには、イングレスコントローラーが必要です。最も広く使われているのは ingress-nginx ですが、運用規模が大きくなりマルチテナンシーの要求が出てくると限界が見え始めます。同じイングレスリソースでアノテーションが数十個に増え、設定を一行変えただけで NGINX 全体がリロードされ、一瞬コネクションが切れることもあります。

私は複数のチームが一つのクラスターを共有する環境を運用しながら、この問題に正面から向き合いました。A チームが誤ったイングレスアノテーションをプッシュすると NGINX 全体の設定検証が失敗し、その影響が B チーム、C チームのサービスにまで波及することが繰り返されました。ルートパスの所有権争いも絶えませんでした。

このとき導入を検討したのが Contour です。Contour は CNCF インキュベーティングプロジェクトで、Envoy プロキシをデータプレーンとして使うイングレスコントローラーです。要点は二つあります。第一に、設定変更時に Envoy をリロードせず xDS API で動的に反映するため、無停止アップデートが可能です。第二に、HTTPProxy という独自の CRD を通じて委譲(delegation)ベースのマルチテナンシーを安全に実装できます。

この記事では、Contour のアーキテクチャから HTTPProxy のルーティングモデル、委譲によるマルチテナンシー、TLS と認証、Gateway API サポート、そして実際の運用で遭遇する落とし穴と解決法までを扱います。2026 年現在の Kubernetes イングレスエコシステムの大きな流れも合わせて押さえていきます。

2026 年のイングレスエコシステムの現状

まず全体像を掴んでおきましょう。Kubernetes のネットワーキング API は今、転換期にあります。

  • Ingress API は凍結(frozen)状態です。 networking.k8s.io/v1 の Ingress リソースには、もはや新機能が追加されません。表現力が不足しているため、コントローラーごとにアノテーションで機能を拡張してきましたが、このアノテーションはコントローラー間で互換性がありません。
  • Gateway API が後継標準です。 SIG-Network が主導する Gateway API は、ロールベースのリソースモデル(GatewayClass、Gateway、HTTPRoute)を提供し、イングレスの表現力の限界とマルチテナンシーの問題を正面から解決するよう設計されています。
  • Contour は三つすべてをサポートします。 Ingress、独自 CRD の HTTPProxy、そして Gateway API を併せてサポートします。HTTPProxy は Gateway API が標準化される前に Contour が先に解決した高度な機能を含んでおり、その経験が Gateway API の設計にも反映されています。

まとめると、新規プロジェクトなら Gateway API を優先的に検討しつつ、Contour の豊富な HTTPProxy 機能(委譲、インクルージョン、きめ細かいトラフィックポリシー)が必要な場合や、すでに HTTPProxy で運用中の場合は、HTTPProxy が依然として強力な選択肢です。

区分IngressHTTPProxyGateway API
標準の有無コア API(凍結)Contour 専用 CRD公式の後継標準
マルチテナンシーアノテーション依存委譲ベース(強力)ロール分離モデル
トラフィックポリシー限定的豊富拡張中
新機能追加なし継続的に追加活発に発展
推奨シナリオレガシー互換高度なマルチテナンシー新標準の採用

Contour のアーキテクチャ - コントロールプレーンとデータプレーンの分離

Contour の設計思想は明確な責務分離です。Contour 自体がコントロールプレーンであり、Envoy がデータプレーンです。

                     +---------------------------+
   kubectl apply     |   Kubernetes API Server    |
   (HTTPProxy / ---> |  (HTTPProxy, Ingress, ...) |
    Ingress)         +-------------+-------------+
                                   |
                          watch (informer)
                                   |
                                   v
                        +----------------------+
                        |   Contour (control   |
                        |   plane / xDS server)|
                        +----------+-----------+
                                   |
                          xDS API (gRPC stream)
                          LDS / RDS / CDS / EDS
                                   |
                                   v
   外部トラフィック -> +----------------------+ ----> +----------------+
   (インターネット)    |   Envoy (data plane) |       | クラスター内部  |
                       |   listener / route / |       | Pod / Service  |
                       |   cluster / endpoint |       +----------------+
                       +----------------------+

動作の流れを段階的に見ていきます。

  1. ユーザーが HTTPProxy や Ingress リソースを kubectl で適用します。
  2. Contour は Kubernetes API サーバーをインフォーマで監視し、変更を検知します。
  3. Contour はこのリソースを内部のオブジェクトグラフに変換し、Envoy が理解する xDS 設定へコンパイルします。
  4. Contour は xDS gRPC ストリームを通じて Envoy に設定をプッシュします。リスナー(LDS)、ルート(RDS)、クラスター(CDS)、エンドポイント(EDS)がそれぞれ動的に更新されます。
  5. Envoy は設定を受け取り無停止で反映し、実際のトラフィックを処理します。

ここで xDS が核心です。NGINX ベースのコントローラーは設定が変わると nginx.conf を書き直しプロセスをリロードします。一方 Envoy は xDS API で設定を動的に受け取るため、プロセスの再起動やリロードがありません。エンドポイントが一つ追加されたりルートルールが変わったりしても、既存のコネクションは影響を受けません。トラフィックが多い環境ほど、この差は大きく感じられます。

デプロイトポロジーも知っておくとよいでしょう。Contour は通常、二つの形態でデプロイされます。

  • Contour Deployment + Envoy DaemonSet: Envoy をすべてのノードに DaemonSet として立ち上げ、hostNetwork や NodePort で公開する方式。
  • Contour Deployment + Envoy Deployment: Envoy を別の Deployment として立ち上げ、LoadBalancer サービスで公開する方式。クラウド環境でよく使われます。

インストール - 素早く立ち上げる

最も簡単な方法は公式マニフェストを適用することです。

# 公式 quickstart マニフェストを適用
kubectl apply -f https://projectcontour.io/quickstart/contour.yaml

# デプロイ状態を確認
kubectl get pods -n projectcontour

# Envoy サービスの外部 IP を確認
kubectl get svc envoy -n projectcontour

本番環境では Helm チャートでインストールして値を管理するほうがすっきりします。

helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-contour bitnami/contour --namespace projectcontour --create-namespace

自分で管理したい場合は、Contour が提供する ContourDeployment CRD と Gateway provisioner を使う方法もあります。コアコンポーネント構成を values で調整した例は次のとおりです。

# contour-values.yaml の例(概念説明用)
contour:
  replicaCount: 2
  resources:
    requests:
      cpu: 100m
      memory: 128Mi
envoy:
  kind: daemonset
  service:
    type: LoadBalancer
  resources:
    requests:
      cpu: 250m
      memory: 256Mi

HTTPProxy の基礎 - 最もシンプルなルーティング

ここからが本題です。HTTPProxy は Contour が定義した CRD で、イングレスの限界を超える豊富なルーティングモデルを提供します。最もシンプルな形から見ていきます。

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: basic-app
  namespace: web
spec:
  virtualhost:
    fqdn: app.example.com
  routes:
    - conditions:
        - prefix: /
      services:
        - name: app-service
          port: 80

virtualhost.fqdn はこのプロキシが処理するドメインです。routes の下で、条件(conditions)にマッチするトラフィックをどのサービスへ送るかを定義します。上の例は app.example.com へ来たすべてのパス(/)を app-service の 80 ポートへ送ります。

複数サービスへの重み付け分割(カナリア)も簡単です。

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: canary-app
  namespace: web
spec:
  virtualhost:
    fqdn: app.example.com
  routes:
    - conditions:
        - prefix: /
      services:
        - name: app-v1
          port: 80
          weight: 90
        - name: app-v2
          port: 80
          weight: 10

weight の値でトラフィックの比率を調整します。上の設定は 90 対 10 で新バージョンへ徐々にトラフィックを流します。新バージョンが安定したら weight を少しずつ調整して移行を完了させます。

条件(conditions) - パスとヘッダーベースのルーティング

HTTPProxy の conditions は prefix パスだけでなくヘッダーベースのマッチングもサポートします。これにより、同じドメインでさまざまなトラフィックの分岐を表現できます。

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: header-routing
  namespace: web
spec:
  virtualhost:
    fqdn: app.example.com
  routes:
    # API パスはバックエンド API サービスへ
    - conditions:
        - prefix: /api
      services:
        - name: api-service
          port: 8080
    # 特定のヘッダーがあればベータサービスへ
    - conditions:
        - prefix: /
        - header:
            name: x-canary
            exact: "true"
      services:
        - name: beta-service
          port: 80
    # それ以外のデフォルトパス
    - conditions:
        - prefix: /
      services:
        - name: web-service
          port: 80

header 条件は exact(完全一致)、contains(含む)、present(存在の有無)、notpresent などをサポートします。上の例では x-canary ヘッダーが true のリクエストのみベータサービスへ送ります。このような分岐はカナリアテストや内部ユーザー向けの機能公開に役立ちます。

ルートレベルでパスの書き換え、ヘッダーの追加/削除、タイムアウト、リトライも指定できます。

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: route-policies
  namespace: web
spec:
  virtualhost:
    fqdn: app.example.com
  routes:
    - conditions:
        - prefix: /legacy
      pathRewritePolicy:
        replacePrefix:
          - prefix: /legacy
            replacement: /v2
      requestHeadersPolicy:
        set:
          - name: x-forwarded-prefix
            value: /legacy
        remove:
          - x-internal-token
      timeoutPolicy:
        response: 30s
      retryPolicy:
        count: 3
        retryOn: 5xx
      services:
        - name: legacy-service
          port: 80

pathRewritePolicy で /legacy を /v2 に書き換え、requestHeadersPolicy でヘッダーを操作し、timeoutPolicy と retryPolicy で回復力ポリシーを指定しました。イングレスではアノテーションの塊で処理していたものが、構造化されたフィールドとしてすっきり表現されます。

インクルージョンと委譲 - マルチテナンシーの核心

ここからが Contour を選ぶ最大の理由です。HTTPProxy は インクルージョン(inclusion) というメカニズムで、ルートプロキシが他のネームスペースの子プロキシを含むことができます。

構造はこうです。プラットフォームチームがドメインと TLS を所有するルート HTTPProxy を管理し、各アプリケーションチームは自分のネームスペースでルートだけを定義する子 HTTPProxy を管理します。ルートが子を特定のパス条件で含みます。

   ルート HTTPProxy (ネームスペース: ingress-root)
   fqdn: example.com, TLS 証明書を所有
        |
        |  include (条件ベースの委譲)
        +-----------------------------+
        |                             |
        v                             v
   子 HTTPProxy                  子 HTTPProxy
   (ネームスペース: team-a)       (ネームスペース: team-b)
   conditions: /team-a          conditions: /team-b
   ルートのみ定義                ルートのみ定義

ルートプロキシは次のようになります。

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: example-root
  namespace: ingress-root
spec:
  virtualhost:
    fqdn: example.com
    tls:
      secretName: example-com-tls
  includes:
    - name: team-a-proxy
      namespace: team-a
      conditions:
        - prefix: /team-a
    - name: team-b-proxy
      namespace: team-b
      conditions:
        - prefix: /team-b

team-a ネームスペースの子プロキシは fqdn なしでルートだけを定義します。

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: team-a-proxy
  namespace: team-a
spec:
  routes:
    - conditions:
        - prefix: /
      services:
        - name: team-a-service
          port: 80

この構造の利点は大きいです。

  • ドメイン所有権の分離: ドメインと TLS はプラットフォームチームが一元的に管理するため、ルートパス争いがなくなります。
  • 権限の分離: アプリケーションチームは自分のネームスペースの HTTPProxy を作る権限を持てば十分です。RBAC ですっきり統制できます。
  • 障害の分離: あるチームの誤った設定はその子プロキシだけを無効化するだけで、他のチームやルート全体には波及しません。Contour は誤った子を拒否し、残りは正常に動作させます。

委譲をさらに統制したい場合は、TLSCertificateDelegation を使って他のネームスペースが特定の証明書シークレットを参照することを明示的に許可することもできます。

apiVersion: projectcontour.io/v1
kind: TLSCertificateDelegation
metadata:
  name: example-delegation
  namespace: ingress-root
spec:
  delegations:
    - secretName: example-com-tls
      targetNamespaces:
        - team-a
        - team-b

TLS 設定 - cert-manager との連携

TLS は virtualhost.tls の下でシークレットを参照するだけで完了します。証明書の発行は cert-manager に任せるのが一般的です。

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com-tls
  namespace: ingress-root
spec:
  secretName: example-com-tls
  dnsNames:
    - example.com
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer

こうして発行されたシークレットを HTTPProxy の tls.secretName で参照します。HTTP から HTTPS へのリダイレクト、最小 TLS バージョン、パススルー(passthrough)モードも設定できます。

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: tls-app
  namespace: web
spec:
  virtualhost:
    fqdn: secure.example.com
    tls:
      secretName: example-com-tls
      minimumProtocolVersion: "1.2"
  routes:
    - conditions:
        - prefix: /
      services:
        - name: secure-service
          port: 443

TCP トラフィックをそのまま転送する TLS passthrough は、tcpproxy と tls.passthrough を組み合わせて構成します。バックエンドが自身で TLS 終端を行う場合に使います。

レートリミットと外部認証

Contour は Envoy の強力な機能をそのまま公開します。代表的なものがレートリミットと外部認証です。

ローカルレートリミットは別途のサービスなしで Envoy 自体で処理します。

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: ratelimited-app
  namespace: web
spec:
  virtualhost:
    fqdn: api.example.com
    rateLimitPolicy:
      local:
        requests: 100
        unit: minute
        burst: 20
  routes:
    - conditions:
        - prefix: /
      services:
        - name: api-service
          port: 80

上の設定は毎分 100 リクエストにバースト 20 を許可します。上限を超えると Envoy が 429 を返します。グローバルレートリミットは外部の RLS(Rate Limit Service)と連携し、クラスター全体で一貫した上限を適用できます。

外部認証は ExtensionService と連携し、すべてのリクエストを認証サーバーへ先に送ります。

apiVersion: projectcontour.io/v1alpha1
kind: ExtensionService
metadata:
  name: authservice
  namespace: auth
spec:
  protocol: h2
  services:
    - name: auth-grpc
      port: 9443
---
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: authenticated-app
  namespace: web
spec:
  virtualhost:
    fqdn: secure.example.com
    tls:
      secretName: example-com-tls
    authorization:
      extensionRef:
        name: authservice
        namespace: auth
  routes:
    - conditions:
        - prefix: /
      services:
        - name: app-service
          port: 80

こうすると secure.example.com へ来るすべてのリクエストが auth ネームスペースの外部認証サービスを経由します。OIDC、JWT 検証などを認証サーバーに委譲できるため、アプリケーションコードから認証ロジックを取り除けます。

ヘルスチェックとロードバランシング戦略

サービスレベルで能動的ヘルスチェックとロードバランシングアルゴリズムを指定できます。

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: balanced-app
  namespace: web
spec:
  virtualhost:
    fqdn: app.example.com
  routes:
    - conditions:
        - prefix: /
      loadBalancerPolicy:
        strategy: WeightedLeastRequest
      services:
        - name: app-service
          port: 80
          healthCheckPolicy:
            path: /healthz
            intervalSeconds: 5
            timeoutSeconds: 2
            unhealthyThresholdCount: 3
            healthyThresholdCount: 2

loadBalancerPolicy.strategy は RoundRobin、WeightedLeastRequest、Random、Cookie(セッションアフィニティ)などをサポートします。healthCheckPolicy で異常なエンドポイントを自動的にプールから除外します。Envoy のアウトライアー検出と組み合わせると回復力が大きく上がります。

Gateway API へ向かう - Contour のサポート

前述のとおり Gateway API はイングレスの後継標準です。Contour は Gateway API の実装として、GatewayClass、Gateway、HTTPRoute をサポートします。同じルーティングを Gateway API で表現すると次のようになります。

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: contour
spec:
  controllerName: projectcontour.io/gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: contour
  namespace: projectcontour
spec:
  gatewayClassName: contour
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: All
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-route
  namespace: web
spec:
  parentRefs:
    - name: contour
      namespace: projectcontour
  hostnames:
    - app.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: app-service
          port: 80

ここで HTTPProxy のインクルージョン/委譲の概念は、Gateway API では Gateway の allowedRoutes と HTTPRoute のネームスペース分離に対応します。ロールベースのモデル(インフラ提供者が GatewayClass、クラスター運用者が Gateway、アプリ開発者が HTTPRoute)が明確に分離されます。

選択基準は次のとおりです。

  • 新規導入で標準互換性が重要なら Gateway API を優先しましょう。
  • HTTPProxy 固有の高度な機能(きめ細かいインクルージョン、特定のトラフィックポリシー)が必要、またはすでに HTTPProxy 資産が多いなら HTTPProxy を維持しましょう。
  • 二つの API を同時に使うことも可能ですが、同じドメインを二つの API が同時に扱わないよう運用ルールを定める必要があります。

運用 - 可観測性と状態確認

運用で最初に確認するのは HTTPProxy の状態です。Contour は各リソースの有効性を status に記録します。

# HTTPProxy の状態確認 - valid / invalid が STATUS に表示される
kubectl get httpproxy -A

# 特定のプロキシの詳細な状態とエラーメッセージ
kubectl describe httpproxy basic-app -n web

status が invalid なら、description フィールドに原因が書かれています。例えば orphaned(ルートが含んでいない子)、重複した fqdn、存在しないサービスへの参照などがよくある原因です。

Envoy の実際の設定を直接覗くには、Contour CLI や Envoy admin インターフェースを活用します。

# Contour が Envoy にプッシュしたルート/クラスター/エンドポイントをダンプ
kubectl exec -n projectcontour deploy/contour -- contour cli endpoints
kubectl exec -n projectcontour deploy/contour -- contour cli clusters

# Envoy の統計(ポートフォワーディング後)
kubectl port-forward -n projectcontour ds/envoy 9001:9001
curl http://localhost:9001/stats | grep upstream_rq

メトリクスは Prometheus で収集します。Contour と Envoy はどちらも Prometheus 形式のメトリクスを公開するので、Grafana ダッシュボードでリクエストレート、レイテンシ、エラー率(4xx/5xx)、アップストリームの健全性を監視します。アクセスログは JSON 形式に設定し、ログパイプラインでパースしやすくすることを推奨します。

パフォーマンスチューニングのポイント

規模が大きくなると次の項目を点検します。

項目推奨事項
Envoy の並行度ノードの CPU に合わせて concurrency(ワーカースレッド)を設定
コネクションプールアップストリームの maxConnections、maxRequests を調整しバックエンドを保護
タイムアウトグローバルなデフォルトタイムアウトとルートごとのタイムアウトを明確に分離
リソース要求/制限Envoy はトラフィックに比例してメモリ/CPU が増えるので余裕を持って
xDS 更新頻度リソースが非常に多い場合は Contour の処理遅延を監視
アクセスログ高トラフィックなら JSON ロギングのコストとディスク I/O を考慮

Envoy の concurrency の値はデータプレーンの性能に直結します。デフォルトはコア数に合わせられますが、DaemonSet でノード全体を占有する環境では明示的に調整するほうが予測可能です。コネクションプールの上限は、遅いバックエンドが全体を引きずり下ろす状況(ヘッドオブラインブロッキング)を防ぐ安全装置です。

よくある落とし穴とトラブルシューティング

運用しながらよく出会う問題を整理します。

1. HTTPProxy の status が invalid なのにトラフィックが来ない

最もよくあります。kubectl describe で description を確認してください。子プロキシがルートによって include されていないと orphaned 状態になりルーティングされません。ルートの includes の name/namespace が正確か確認します。

2. 同じ fqdn を複数のルートプロキシが宣言している

同じドメインを二つのルート HTTPProxy が宣言すると、Contour は競合とみなし片方を無効化します。fqdn はクラスター全体で一意でなければなりません。マルチテナンシーは fqdn を分けず、インクルージョンでパスを分けてください。

3. 503 upstream connect error

ほとんどがサービス/エンドポイントの問題です。対象 Service のセレクターが実際の Pod と合っているか、Endpoints が空でないか確認します。contour cli endpoints で Envoy が受け取ったエンドポイントを確認すると速いです。

4. TLS 証明書が適用されない

secretName が指すシークレットが同じネームスペースにあるか、別のネームスペースなら TLSCertificateDelegation で委譲しているか確認します。cert-manager が発行中なら Certificate リソースの Ready 状態を確認します。

5. パスの書き換えが意図と違って動作する

replacePrefix はマッチした prefix 部分だけを置換します。prefix 条件と replacePrefix の prefix が一致しないと、予想と違う結果になります。条件の prefix と書き換えルールを合わせて点検してください。

6. 設定の反映遅延

Contour がリソースを処理するのに時間がかかる、または Envoy への xDS プッシュが遅い場合です。リソース数が数千規模なら Contour のログと処理遅延メトリクスを確認し、必要なら Contour のレプリカとリソースを増やします。

診断コマンドを一箇所にまとめると次のとおりです。

# 全 HTTPProxy の状態を一目で
kubectl get httpproxy -A -o wide

# Contour のログからエラーを追跡
kubectl logs -n projectcontour deploy/contour --tail=200 | grep -i error

# Envoy が受け取ったクラスター/エンドポイントを確認
kubectl exec -n projectcontour deploy/contour -- contour cli clusters
kubectl exec -n projectcontour deploy/contour -- contour cli endpoints

# Envoy admin の統計でアップストリームの状態を確認
kubectl port-forward -n projectcontour ds/envoy 9001:9001
curl -s http://localhost:9001/clusters | grep health_flags

おわりに

Contour は単純なイングレスコントローラーを超え、Envoy の動的設定能力と HTTPProxy の委譲モデルを組み合わせて、マルチテナンシー環境で輝くツールです。要点を改めて整理すると次のとおりです。

  • xDS ベースの無停止設定反映により、トラフィックの多い環境でも安定したアップデートが可能です。
  • インクルージョンと委譲でドメイン所有権を分離し、障害を分離し、RBAC で権限を統制する安全なマルチテナンシーを実装します。
  • 豊富なトラフィックポリシー(レートリミット、外部認証、ヘルスチェック、リトライ)をアノテーションの塊なしに構造化されたフィールドで扱います。
  • Gateway API サポートで、将来の標準への移行経路を確保します。

2026 年現在、Ingress API は凍結され、Gateway API が後継標準として定着しつつあります。新規プロジェクトなら Gateway API を優先的に検討しつつ、Contour の HTTPProxy は依然として強力なマルチテナンシーツールとしてその価値を保っています。両方の道を Contour で行けることが、このプロジェクトの大きな利点です。

ぜひ自分でクラスターに Contour を立ち上げ、小さな子 HTTPProxy を一つ作って委譲がどう動作するかを目で確かめてみてください。アノテーション地獄から抜け出し、構造化されたトラフィック管理の心地よさを感じられるはずです。

参考資料