Go K8s Gateway APIプロダクションガイド:Ingressからの移行の6つの重要ステップ

云原生

Ingressがボトルネックになる時:K8sトラフィック管理の至暗の刻

深夜2時、運用チームがカナリアリリースを緊急デプロイ。Ingressのnginxアノテーションを3回修正したが、nginx.ingress.kubernetes.io/canary設定が一向に有効にならない。さらに問題なのは、3つのチームが1つのIngressコントローラーを共有しており、アノテーションの変更が互いに上書きされる危険があること。最終的にカナリアリリースはロールバックされ、デプロイは6時間遅延した。

これは決して例外ではない。Ingressのアノテーション地獄、クロスネームスペースの制限、マルチチーム協作の衝突は、K8sトラフィック管理の最大のペインポイントとなっている。Kubernetes Gateway APIはIngressの後継として、ロールベース設計、拡張可能なルーティング、ネイティブトラフィック分割により、これらの問題を根本的に解決する。本記事では6つの重要ステップで、IngressからGateway APIへのプロダクション級移行を案内する。


コア概念クイックリファレンス

リソース ロール 責務 例え
GatewayClass インフラプロバイダー コントローラータイプとパラメータを定義 IngressClass
Gateway クラスタ運用者 エントリリスナーとインフラを定義 Ingressコントローラー自体
HTTPRoute アプリ開発者 HTTPルーティングルールを定義 Ingressルール
TCPRoute アプリ開発者 TCPルーティングルールを定義 Ingressにネイティブサポートなし
GRPCRoute アプリ開発者 gRPCルーティングルールを定義 Ingressにネイティブサポートなし
ReferenceGrant クラスタ運用者 クロスネームスペース参照を許可 該当なし
ParentRef アプリ開発者 RouteをGatewayにバインド IngressのingressClassName

目次

  1. 問題分析:Ingressの5つの課題
  2. ステップ1:GatewayClassとインフラ設定
  3. ステップ2:Gatewayリソース定義
  4. ステップ3:HTTPRouteルーティングルール設定
  5. ステップ4:IngressからGateway APIへの移行スクリプト(Go)
  6. ステップ5:カナリアリリースとトラフィック分割
  7. ステップ6:マルチクラスターゲートウェイ設定
  8. 5つのよくある落とし穴
  9. 10のエラートラブルシューティング
  10. 高度な最適化テクニック
  11. 比較:Ingress vs Gateway API vs カスタムゲートウェイ
  12. おすすめツール
  13. まとめと参考資料

問題分析:Ingressの5つの課題

課題1:アノテーションの不整合。Nginx Ingressはnginx.ingress.kubernetes.io/rewrite-targetを使用し、Traefikはtraefik.ingress.kubernetes.io/rewrite-targetを使用する。アノテーションの非互換性により、コントローラー移行のコストが極めて高い。

課題2:クロスネームスペースルーティング。Ingressのバックエンドは同じネームスペースのServiceのみ参照可能。クロスネームスペースのトラフィックにはExternalName Serviceのハックが必要で、安全でなく制御も困難。

課題3:マルチクラスターゲートウェイ。Ingressは単一クラスターのみを考慮して設計されている。マルチクラスターのシナリオでは、Federationやカスタムコントロールプレーンの追加が必要。

課題4:カナリアリリースの複雑さ。Nginx IngressのCanaryアノテーションでは2つのIngressオブジェクトの作成が必要。ウェイト設定が直感的でなく、通常ルーティングと混在して管理が困難。

課題5:プロトコルサポートの制限。IngressはHTTP/HTTPSのみサポート。TCP/UDPには追加設定が必要、gRPCはアノテーションハックが必要、WebSocketサポートはコントローラーにより異なる。


ステップ1:GatewayClassとインフラ設定

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: production-gateway-class
  annotations:
    gateway.networking.k8s.io/controller: nginx
spec:
  controllerName: k8s.io/nginx-gateway-controller
  parametersRef:
    group: ""
    kind: ConfigMap
    name: gateway-params
    namespace: gateway-system
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: gateway-params
  namespace: gateway-system
data:
  workerProcesses: "auto"
  maxConnections: "10240"
  proxyBufferSize: "16k"

GatewayClassはインフラチームが管理し、コントローラータイプとグローバルパラメータを定義する。異なる環境で異なるGatewayClassを使用し、開発/ステージング/プロダクションの分離を実現する。


ステップ2:Gatewayリソース定義

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: production-gateway
  namespace: gateway-system
  annotations:
    cert-manager.io/issuer: letsencrypt-prod
spec:
  gatewayClassName: production-gateway-class
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              shared-gateway-access: "true"
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-tls
            namespace: gateway-system
      allowedRoutes:
        namespaces:
          from: All
  addresses:
    - type: IPAddress
      value: 10.0.1.100

Gatewayは運用チームが管理し、リスナー、TLS終端、許可ルートネームスペースを定義する。allowedRoutesはきめ細かいネームスペースアクセス制御を実現し、Ingressの暗黙的なグローバルアクセスに代わる。


ステップ3:HTTPRouteルーティングルール設定

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-service-route
  namespace: app-team-a
spec:
  parentRefs:
    - name: production-gateway
      namespace: gateway-system
      sectionName: https
  hostnames:
    - "api.example.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v2
        - headers:
            - type: Exact
              name: X-API-Version
              value: "2"
      backendRefs:
        - name: api-v2-service
          port: 8080
          weight: 90
        - name: api-v2-canary-service
          port: 8080
          weight: 10
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: api-v1-service
          port: 8080

HTTPRouteはアプリチームが管理し、パスマッチング、ヘッダーマッチング、ウェイトベースのトラフィック分割をサポートする。parentRefsは特定のGatewayリスナーにバインドし、weightはネイティブトラフィック分割を可能にする。


ステップ4:IngressからGateway APIへの移行スクリプト(Go)

package main

import (
    "context"
    "fmt"
    "os"

    networkingv1 "k8s.io/api/networking/v1"
    gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
)

type IngressMigrator struct {
    clientset    *kubernetes.Clientset
    gatewayClass string
    gatewayName  string
    gatewayNS    string
}

func NewIngressMigrator(kubeconfig, gatewayClass, gatewayName, gatewayNS string) (*IngressMigrator, error) {
    config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
    if err != nil {
        return nil, fmt.Errorf("build kubeconfig: %w", err)
    }
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        return nil, fmt.Errorf("create clientset: %w", err)
    }
    return &IngressMigrator{
        clientset:    clientset,
        gatewayClass: gatewayClass,
        gatewayName:  gatewayName,
        gatewayNS:    gatewayNS,
    }, nil
}

func (m *IngressMigrator) Migrate(ctx context.Context, namespace string) ([]*gatewayv1.HTTPRoute, error) {
    ingresses, err := m.clientset.NetworkingV1().Ingresses(namespace).List(ctx, metav1.ListOptions{})
    if err != nil {
        return nil, fmt.Errorf("list ingresses: %w", err)
    }

    var routes []*gatewayv1.HTTPRoute
    for _, ing := range ingresses.Items {
        route, err := m.convertIngressToHTTPRoute(&ing)
        if err != nil {
            fmt.Fprintf(os.Stderr, "skip ingress %s: %v\n", ing.Name, err)
            continue
        }
        routes = append(routes, route)
    }
    return routes, nil
}

func (m *IngressMigrator) convertIngressToHTTPRoute(ing *networkingv1.Ingress) (*gatewayv1.HTTPRoute, error) {
    route := &gatewayv1.HTTPRoute{
        TypeMeta: metav1.TypeMeta{
            APIVersion: "gateway.networking.k8s.io/v1",
            Kind:       "HTTPRoute",
        },
        ObjectMeta: metav1.ObjectMeta{
            Name:        ing.Name + "-route",
            Namespace:   ing.Namespace,
            Annotations: filterGatewayAnnotations(ing.Annotations),
        },
        Spec: gatewayv1.HTTPRouteSpec{
            ParentRefs: []gatewayv1.ParentReference{
                {
                    Name:      gatewayv1.ObjectName(m.gatewayName),
                    Namespace: (*gatewayv1.Namespace)(&m.gatewayNS),
                },
            },
        },
    }

    for _, rule := range ing.Spec.Rules {
        if rule.Host != "" {
            hostname := gatewayv1.PreciseHostname(rule.Host)
            route.Spec.Hostnames = append(route.Spec.Hostnames, hostname)
        }
        for _, path := range rule.HTTP.Paths {
            matchType := gatewayv1.PathMatchPathPrefix
            if path.PathType != nil && *path.PathType == networkingv1.PathTypeExact {
                matchType = gatewayv1.PathMatchExact
            }
            route.Spec.Rules = append(route.Spec.Rules, gatewayv1.HTTPRouteRule{
                Matches: []gatewayv1.HTTPRouteMatch{
                    {
                        Path: &gatewayv1.HTTPPathMatch{
                            Type:  &matchType,
                            Value: &path.Path,
                        },
                    },
                },
                BackendRefs: []gatewayv1.HTTPBackendRef{
                    {
                        BackendRef: gatewayv1.BackendRef{
                            BackendObjectReference: gatewayv1.BackendObjectReference{
                                Name: gatewayv1.ObjectName(path.Backend.Service.Name),
                                Port: (*gatewayv1.PortNumber)(&path.Backend.Service.Port.Number),
                            },
                        },
                    },
                },
            })
        }
    }
    return route, nil
}

func filterGatewayAnnotations(annotations map[string]string) map[string]string {
    filtered := make(map[string]string)
    for k, v := range annotations {
        if k == "kubernetes.io/ingress.class" || k == "nginx.ingress.kubernetes.io/rewrite-target" {
            continue
        }
        filtered[k] = v
    }
    return filtered
}

func main() {
    kubeconfig := os.Getenv("KUBECONFIG")
    if kubeconfig == "" {
        kubeconfig = clientcmd.RecommendedHomeFile
    }

    migrator, err := NewIngressMigrator(kubeconfig, "production-gateway-class", "production-gateway", "gateway-system")
    if err != nil {
        fmt.Fprintf(os.Stderr, "init migrator: %v\n", err)
        os.Exit(1)
    }

    routes, err := migrator.Migrate(context.Background(), "default")
    if err != nil {
        fmt.Fprintf(os.Stderr, "migrate: %v\n", err)
        os.Exit(1)
    }

    for _, route := range routes {
        fmt.Printf("Generated HTTPRoute: %s/%s\n", route.Namespace, route.Name)
    }
    fmt.Printf("Total: %d routes migrated\n", len(routes))
}

ステップ5:カナリアリリースとトラフィック分割

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-canary-route
  namespace: app-team-a
spec:
  parentRefs:
    - name: production-gateway
      namespace: gateway-system
  hostnames:
    - "api.example.com"
  rules:
    - matches:
        - headers:
            - type: Exact
              name: X-Canary
              value: "true"
      backendRefs:
        - name: api-v2-service
          port: 8080
          weight: 100
    - backendRefs:
        - name: api-v1-service
          port: 8080
          weight: 95
        - name: api-v2-service
          port: 8080
          weight: 5

Gateway APIはヘッダーベースとウェイトベースのトラフィック分割をネイティブサポートし、NginxのCanaryアノテーションは不要。5%のトラフィックから開始し、徐々に100%まで増加させてカナリアリリースを完了する。


ステップ6:マルチクラスターゲートウェイ設定

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: multi-cluster-gateway-class
spec:
  controllerName: k8s.io/nginx-gateway-controller
  parametersRef:
    group: multicluster.x-k8s.io
    kind: ServiceImport
    name: multi-cluster-params
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: cross-cluster-route
  namespace: app-team-a
spec:
  parentRefs:
    - name: production-gateway
      namespace: gateway-system
  hostnames:
    - "api.example.com"
  rules:
    - backendRefs:
        - name: api-service-cluster-east
          port: 8080
          weight: 70
        - name: api-service-cluster-west
          port: 8080
          weight: 30

マルチクラスターゲートウェイはServiceImportとMulti-Cluster Service APIを使用してクラスタ間トラフィック分散を実現し、ウェイト制御でリージョンアフィニティとフェイルオーバーを実現する。


5つのよくある落とし穴

❌ 落とし穴1:GatewayとRouteを同じネームスペースに配置 ✅ Gatewayはgateway-systemに、Routeはアプリケーションネームスペースに配置し、allowedRoutesでアクセス権を制御する。ロール分離はGateway APIのコア設計原則。

❌ 落とし穴2:クロスネームスペース参照のReferenceGrant設定を忽略 ✅ クロスネームスペースでServiceやSecretを参照する場合、対象ネームスペースにReferenceGrantを作成する必要がある。さもなくばルートは有効にならない。

❌ 落とし穴3:Gatewayを作成する前にIngressを削除 ✅ まずGatewayとHTTPRouteを作成し、ルートの動作を確認してからIngressを削除する。スムーズな移行のために。

❌ 落とし穴4:HTTPRouteのウェイト合計が100にならない ✅ ウェイトは相対値であり、合計は100である必要はないが、理解と計算を容易にするため合計100を維持することを推奨。

❌ 落とし穴5:GatewayClassにparametersRefを指定しない ✅ プロダクションでは必ずparametersRefでコントローラーパラメータを設定し、デフォルト値によるパフォーマンスボトルネックを回避する。


10のエラートラブルシューティング

エラー症状 考えられる原因 デバッグコマンド 解決策
HTTPRouteステータスが空 parentRefが存在しないGatewayを指している kubectl get gateway -A Gateway名とネームスペースを確認
ルートが有効にならない allowedRoutesがRouteネームスペースを許可していない kubectl describe gateway ネームスペースラベルを追加またはAllに変更
TLS証明書がロードされない certificateRefsのSecretが存在しない kubectl get secret -n gateway-system TLS Secretを作成
クロスネームスペースルートが失敗 ReferenceGrantが不足 kubectl get referencegrant -A ReferenceGrantを作成して参照を許可
カナリアトラフィックが分割されない weightが0または未設定 kubectl describe httproute backendRefsのweight値を確認
GatewayClassが利用不可 コントローラーPodが実行されていない kubectl get pods -n gateway-system Gatewayコントローラーを起動
TCPRouteが動作しない GatewayがTCPポートでリッスンしていない kubectl describe gateway TCPプロトコルのリスナーを追加
GRPCRouteルーティングが失敗 バックエンドServiceがgRPCをサポートしていない kubectl describe svc ServiceがgRPCプロトコルをサポートしているか確認
移行スクリプトが404を返す client-goバージョンがクラスターと非互換 kubectl version client-goとK8sバージョンを一致させる
マルチクラスタールートが不均衡 ServiceImportの設定ミス kubectl get serviceimport -A Multi-Cluster Service設定を確認

高度な最適化テクニック

1. Gatewayインフラ分離。異なるSLAのサービスに対して異なるGatewayClassを作成する。コアサービスは専用Gatewayインスタンスを使用し、共有インフラの干渉を回避する。

2. ルートポリシーの組み合わせ。HTTPRouteはネストされたmatchesをサポートし、パス、ヘッダー、クエリパラメータを組み合わせてきめ細かいルーティングを実現。例:/api/v2パスとX-Feature-Flag: enabledヘッダーの同時マッチ。

3. タイムアウトとリトライポリシーbackendReqTimeoutとリトライポリシーアノテーションを使用して、ルーティングレイヤーでタイムアウト制御と自動リトライを実装し、アプリケーションレベルのコードを削減。

4. オブザーバビリティ統合。Gateway APIはPolicyAttachmentを通じてOpenTelemetry統合をサポートし、ルートレベルのTraceとMetrics収集を実現。

5. 段階的移行kubectl apply --dry-run=serverでYAMLを検証し、kubectl diffで変更をプレビューして、ゼロリスク移行を確保。


比較:Ingress vs Gateway API vs カスタムゲートウェイ

特徴 Ingress Gateway API カスタムゲートウェイ
ロール分離 ❌ 全て混在 ✅ インフラ/運用/開発の分離 ⚠️ 実装による
クロスネームスペース ❌ サポートなし ✅ ReferenceGrant ✅ 自由設定
トラフィック分割 ⚠️ アノテーションハック ✅ ネイティブweight ✅ 自由設定
TCP/gRPC ❌ アノテーションハック ✅ ネイティブサポート ✅ 自由設定
マルチクラスター ❌ サポートなし ⚠️ MCS APIが必要 ✅ 自由設定
ポータビリティ ⚠️ アノテーション非互換 ✅ 標準化API ❌ 完全に結合
学習コスト
運用コスト
コミュニティサポート ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐
プロダクション推奨度 ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐

おすすめツール

  • JSONフォーマッター — Gateway APIのYAML/JSON設定をフォーマット、リソース定義の問題を素早く特定
  • ハッシュ計算ツール — TLS証明書とConfigMapのチェックサムを計算、ゲートウェイ設定データの整合性を確保
  • cURL to Code — cURLテストコマンドをGoコードに変換、Gateway APIクライアント開発を加速

まとめ

Gateway APIはIngressの単なるアップグレードではなく、K8sトラフィック管理のパラダイムシフトである。「1つのIngressで全てを管理」から「ロール分離、責務の明確化」へ、「アノテーション地獄」から「標準化API」へ、「単一クラスター」から「ネイティブマルチクラスター対応」へ。6つの移行ステップ——GatewayClass定義、Gateway設定、HTTPRouteルーティング、Go移行スクリプト、カナリアトラフィック分割、マルチクラスターゲートウェイ——がプロダクション移行の完全なチェーンをカバーする。覚えておくべきは:先に作成してから削除、段階的移行、各ステップを検証——これがゼロダウンタイム移行の鍵である。


参考資料

ブラウザローカルツールを無料で試す →

#Kubernetes Gateway API#K8s网关#Ingress迁移#Go#流量管理#2026#云原生