本記事には広告(アフィリエイトリンク)が含まれます。

Kubernetes応用編 第05回

Kubernetes応用編 第05回
外部トラフィックの入口設計 — Gateway API

5.1 はじめに

前回(第4回)では、Deployment以外のワークロードリソース — Job、CronJob、DaemonSet — をTaskBoardに追加しました。DB初期化をJobで自動化し、定期バックアップをCronJobで仕組み化し、ログ収集をDaemonSetで全Worker Nodeに配置しました。5種類のワークロードリソースを要件に応じて使い分ける判断力が身についたはずです。

現在のTaskBoardの状態を確認しておきましょう。

[app Namespace]
  Nginx (Deployment, replicas: 2) + Service (ClusterIP)
  TaskBoard API (Deployment, replicas: 2, MySQL接続版) + Service (NodePort: 30080)
  + ResourceQuota / LimitRange 適用済み
  + RBAC適用済み

[db Namespace]
  MySQL (StatefulSet, replicas: 1) + Headless Service + PVC
  + DB初期化Job(Completed)
  + DBバックアップCronJob(稼働中)
  + Secret(MySQL認証情報)
  + ResourceQuota / LimitRange 適用済み
  + RBAC適用済み

[monitoring Namespace]
  ログ収集DaemonSet(全Worker Nodeに配置)
  + ResourceQuota / LimitRange 適用済み
  + RBAC適用済み

[クラスタ全体]
  Metrics Server 稼働中
  マルチノード構成(CP 1 + Worker 3)

ここでひとつ問題があります。TaskBoard APIへのアクセスに、NodePort(30080番)を使っています。第1回で「第5回でGateway APIに切り替えるまでの一時的な手段」と説明したNodePortです。

NodePortは手軽ですが、本番環境での入口設計としては力不足です。サービスが増えるたびにポート番号が増え、「30080はAPI、30081はフロント…」と管理が煩雑になります。さらに、パスによる振り分け(/ はフロントへ、/api はAPIへ)もできません。VMの世界でいえば、各アプリケーションサーバーのポートを直接ファイアウォールで公開しているような状態です。

今回は、KubernetesのGateway APIを導入して、この問題を解決します。

BeforeAfter
NodePortでServiceに直接アクセスしている。入口が複数あって管理しにくいGateway APIでL7ルーティングを構築し、パスベースの振り分けができる

具体的には、以下を達成します。

  • Gateway APIコントローラー(Envoy Gateway)をクラスタに導入する
  • GatewayClass → Gateway → HTTPRoute の3層構造を段階的に構築する
  • パスベースルーティングで / → Nginx、/api → TaskBoard API に振り分ける
  • NodePortとの違いを体感し、外部アクセス設計の判断基準を身につける

今回から3回にわたる「ネットワークとセキュリティ」セクションの第1弾です。まずは「入口」を整えるところから始めましょう。

5.2 VMの世界との対比 — ロードバランサーとVirtual Server

5.2.1 F5 BIG-IP / NSXロードバランサーの役割

VMware環境で複数のWebアプリケーションを外部に公開する場合、一般的にはF5 BIG-IPやNSXロードバランサーを使います。構成を思い出してみましょう。

まず、ロードバランサーに「Virtual Server」を定義します。外部からのリクエストはすべてこのVirtual ServerのIPアドレスに届きます。次に、iRuleやL7ポリシーでURLパスを見て、振り分け先の「Pool」を決めます。Poolの中には実際のVMのIPアドレスとポート番号が「Pool Member」として登録されています。

外部クライアント
    │
    ▼
Virtual Server (VIP: 10.0.1.100:80)
    │
    ├── /       → Pool: frontend-pool  → VM-1:80, VM-2:80
    │
    └── /api    → Pool: api-pool       → VM-3:8080, VM-4:8080

この構成のポイントは3つです。外部に公開するIPアドレス(入口)は1つ。パスによって振り分け先を変えるL7ルーティング。そして、バックエンドのVM群をPoolとして抽象化する仕組み。これが「ロードバランサー設計」の基本形です。

5.2.2 K8sではGateway APIがその役割を担う

KubernetesのGateway APIは、まさにこのロードバランサー設計をKubernetesネイティブに実現するものです。VMの世界の概念とほぼ1対1で対応します。

VMの世界(F5 BIG-IP / NSX LB)Kubernetes(Gateway API)
ロードバランサー製品の種類(F5, NSX, HAProxy…)GatewayClass
Virtual Server(VIP + リスナー設定)Gateway
iRule / L7ポリシー + Pool定義HTTPRoute
Pool Member(VMのIP:ポート)Service(ClusterIP)のエンドポイント

「GatewayClassでロードバランサーの種類を選び、Gatewayで入口(リスナー)を定義し、HTTPRouteでパスごとの振り分け先を設定する」—— この3層構造を理解すれば、Gateway APIの全体像はつかめます。次のセクションで、この3層構造を詳しく見ていきましょう。

5.3 Gateway APIの全体像

5.3.1 Gateway APIの3層構造 — GatewayClass / Gateway / HTTPRoute

Gateway APIは、3つのリソースが役割を分担する階層構造になっています。

┌─────────────────────────────────────────────────┐
│  GatewayClass                                   │
│  「どのコントローラー(実装)を使うか」         │
│  → クラスタ管理者が設定                         │
├─────────────────────────────────────────────────┤
│  Gateway                                        │
│  「どのポートで、どのプロトコルで待ち受けるか」 │
│  → インフラ管理者が設定                         │
├─────────────────────────────────────────────────┤
│  HTTPRoute                                      │
│  「どのパスを、どのServiceに振り分けるか」      │
│  → アプリケーション開発者が設定                 │
└─────────────────────────────────────────────────┘

GatewayClassは、Gateway APIを実際に動かすコントローラーの種類を定義します。VMの世界でいえば「ロードバランサー製品の選定」にあたります。F5を使うのか、NSXを使うのか—— それをKubernetesリソースとして宣言するのがGatewayClassです。GatewayClassはクラスタスコープのリソースであり、クラスタ全体で共有されます。

Gatewayは、実際にトラフィックを受け付けるエントリポイントです。「HTTPの80番ポートで待ち受ける」といったリスナー設定を定義します。VMの世界のVirtual Server(VIP + ポート)に対応します。GatewayはNamespaceに所属するリソースです。

HTTPRouteは、受け取ったリクエストをどのServiceに振り分けるかのルールです。「パス /api で始まるリクエストはtaskboard-api Serviceへ」「それ以外は nginx Serviceへ」といったルーティングルールを定義します。VMの世界のiRuleやL7ポリシー + Pool定義に対応します。

この3層構造の最大の利点は「関心の分離」です。クラスタ管理者はGatewayClassを選び、インフラ担当はGatewayでリスナーを設定し、アプリ開発者はHTTPRouteでルーティングを定義する。それぞれが自分の責任範囲だけを設定すればよい仕組みになっています。

5.3.2 旧Ingressとの違い — なぜGateway APIが今の推奨か

Kubernetesには以前から「Ingress」というL7ルーティングのリソースがありました。現在も動作しますが、Gateway APIへの移行が推奨されています。その理由を整理しておきましょう。

Ingressは1つのリソースにリスナー設定とルーティングルールが混在しており、設定が複雑化しやすい構造でした。また、Ingress自体の仕様がシンプルすぎたため、高度な機能(ヘッダーベースルーティング、トラフィックの重み付け分散など)はコントローラー固有のアノテーションで実現するしかありませんでした。nginx-ingressとtraefik-ingressで設定方法がまったく異なるといった、ポータビリティの問題が常につきまといました。

Gateway APIはこれらの課題を設計段階から解決しています。3層構造による関心の分離に加え、HTTPRouteの仕様自体にヘッダーマッチングやウェイトベースルーティングが含まれているため、コントローラー固有のアノテーションに頼る必要がありません。Gateway APIはKubernetes SIG-Networkが策定した公式仕様(v1としてGA)であり、主要なコントローラーがこの仕様に準拠しています。

観点IngressGateway API
リソースの構造1リソースに集約(リスナー + ルール)3層に分離(GatewayClass / Gateway / HTTPRoute)
高度な機能コントローラー固有のアノテーションに依存仕様に標準で含まれる(ヘッダーマッチ、重み付け等)
ポータビリティコントローラーごとに設定方法が異なる共通仕様に準拠(コントローラーを変えてもHTTPRouteはそのまま)
権限の分離困難(1リソースに全設定)3層構造により自然に分離される
ステータス安定版だが後継が推奨されているv1 GA(Kubernetes公式の推奨仕様)

本シリーズでは最初からGateway APIを使います。Ingressの手順は掲載しません。これから学ぶなら、新しい標準を身につけるほうが将来の投資として正しい選択です。

5.3.3 Gateway APIコントローラーの役割

Gateway API自体はリソースの「仕様」(CRD: Custom Resource Definition)です。マニフェストを書いてapplyしただけでは、実際にトラフィックを処理するものは何も動きません。仕様を実際に動かす「コントローラー」が別途必要です。

VMの世界に例えると、Gateway APIの仕様は「ロードバランサーの設定書式」であり、コントローラーは「実際にトラフィックを受けるF5やNSXの実機」です。設定書式だけあっても、実機がなければトラフィックは流れません。

Gateway APIコントローラーの選択肢は複数あります。Envoy Gateway、Istio、Contour、Traefik、NGINX Gatewayなどです。本シリーズではEnvoy Gatewayを採用します。選定理由は以下の通りです。

  • Envoy Proxyをベースとしたリファレンス実装に近い位置づけであり、Gateway APIの標準仕様に忠実
  • Kubernetes SIG-Networkと密接に連携したプロジェクトであり、Gateway APIの新機能への追従が早い
  • kind環境での動作実績が豊富(公式ドキュメント自体がkindでの利用を想定している)
  • kubectl apply 1コマンドで導入が完結し、追加の設定がほぼ不要

コントローラーの選定は本番環境では重要な判断ですが、本回の主題はGateway APIの使い方を学ぶことです。コントローラーの比較検討は実践編で改めて扱います。

5.4 Gateway APIコントローラーを導入する

5.4.1 コントローラーの選定と導入

まず、第4回終了時の環境が正常に稼働していることを確認します。

[Execution User: developer]

kubectl get nodes
NAME                        STATUS   ROLES           AGE   VERSION
k8s-applied-control-plane   Ready    control-plane   ●d    v1.32.x
k8s-applied-worker          Ready    <none>          ●d    v1.32.x
k8s-applied-worker2         Ready    <none>          ●d    v1.32.x
k8s-applied-worker3         Ready    <none>          ●d    v1.32.x

[Execution User: developer]

kubectl get pods -n app
kubectl get pods -n db
kubectl get pods -n monitoring

app、db、monitoringの各NamespaceでPodが正常に稼働していることを確認してください。TaskBoard APIがMySQLに接続できていることも簡単に確認しておきましょう。

[Execution User: developer]

kubectl exec -n app deploy/taskboard-api -- curl -s http://localhost:8080/api/tasks

タスクの一覧(JSONの配列)が返ってくれば、TaskBoard APIは正常です。

では、Envoy Gatewayを導入しましょう。公式が提供するインストール用YAMLを1コマンドでapplyします。このYAMLにはGateway APIのCRD(Custom Resource Definition)、Envoy Gatewayコントローラー本体、必要なRBAC設定がすべて含まれています。

[Execution User: developer]

kubectl apply --server-side -f https://github.com/envoyproxy/gateway/releases/download/v1.6.3/install.yaml

多数のリソースが作成されます。--server-side フラグはサーバーサイドApplyを有効にするもので、CRDのような大きなリソースを確実にapplyするために必要です。

コントローラーが起動するまで待ちましょう。

[Execution User: developer]

kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available
deployment.apps/envoy-gateway condition met

Envoy Gatewayは envoy-gateway-system Namespaceに配置されます。Podの状態を確認しましょう。

[Execution User: developer]

kubectl get pods -n envoy-gateway-system
NAME                             READY   STATUS    RESTARTS   AGE
envoy-gateway-6f8b9d7c4f-xxxxx   1/1     Running   0          60s

Envoy Gatewayコントローラーが稼働しました。このPodは「コントロールプレーン」として動作します。実際にトラフィックを受けるEnvoy Proxy(データプレーン)のPodは、Gatewayリソースを作成した時点で自動的に生成されます。

5.4.2 GatewayClassを確認する

Envoy Gatewayのインストールにより、GatewayClassリソースが自動的に作成されています。確認しましょう。

[Execution User: developer]

kubectl get gatewayclasses
NAME   CONTROLLER                                      ACCEPTED   AGE
eg     gateway.envoyproxy.io/gatewayclass-controller   True       90s

eg という名前のGatewayClassが作成されています。ACCEPTED: True は、コントローラーがこのGatewayClassを認識し、管理する準備ができていることを示しています。

GatewayClassの詳細を見てみましょう。

[Execution User: developer]

kubectl describe gatewayclass eg

controllerName フィールドに gateway.envoyproxy.io/gatewayclass-controller と表示されます。これはEnvoy Gatewayのコントローラー名です。Gatewayリソースでこの eg GatewayClassを参照すると、Envoy Gatewayがそのトラフィック処理を担当することになります。

VMの世界に例えるなら、GatewayClassは「このクラスタではEnvoy(≒F5相当のロードバランサー製品)を使います」という宣言です。製品の選定が終わったので、次はVirtual Serverの設定 — つまりGatewayリソースの作成に進みます。

5.5 TaskBoardにGateway APIを設定する

5.5.1 Gatewayを作成する

Gatewayリソースは「トラフィックの入口」を定義します。どのプロトコルで、どのポートで待ち受けるかを指定します。今回はHTTPの80番ポートで待ち受けるGatewayを作成します。

[Execution User: developer]

cat <<'EOF' > ~/k8s-applied/gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: taskboard-gateway
  namespace: app
spec:
  # 使用するGatewayClass — Envoy Gatewayが管理する
  gatewayClassName: eg
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          # 同じNamespace(app)内のHTTPRouteのみ許可
          from: Same
EOF

ポイントを整理します。gatewayClassName: eg で、先ほど確認したEnvoy GatewayのGatewayClassを参照しています。listeners セクションでHTTPプロトコル・80番ポートのリスナーを1つ定義しています。allowedRoutes.namespaces.from: Same は、このGatewayにアタッチできるHTTPRouteを同じNamespace(app)内に限定する設定です。別のNamespaceのHTTPRouteが勝手にルートを追加することを防ぎます。

マニフェストをapplyしましょう。

[Execution User: developer]

kubectl apply -f ~/k8s-applied/gateway.yaml
gateway.gateway.networking.k8s.io/taskboard-gateway created

Gatewayの状態を確認します。

[Execution User: developer]

kubectl get gateway -n app
NAME                CLASS   ADDRESS          PROGRAMMED   AGE
taskboard-gateway   eg      172.18.0.x       True         30s

PROGRAMMED: True は、Envoy Gatewayがこの設定を受け取り、データプレーン(Envoy Proxy)に反映したことを示します。ADDRESS 列にIPアドレスが表示されていますが、これはkind環境内部のアドレスです。

ここで、Envoy Gatewayが自動的にデータプレーンPod(実際にトラフィックを処理するEnvoy Proxy)を作成したことを確認しましょう。

[Execution User: developer]

kubectl get pods -n envoy-gateway-system
NAME                                                 READY   STATUS    RESTARTS   AGE
envoy-app-taskboard-gateway-xxxxxxxxxx-xxxxx         2/2     Running   0          30s
envoy-gateway-6f8b9d7c4f-xxxxx                       1/1     Running   0          5m

新たに envoy-app-taskboard-gateway- で始まるPodが作成されています。これがデータプレーンです。コントロールプレーン(envoy-gateway-)が設定を管理し、データプレーンが実際のトラフィック処理を行う — この役割分担はVMの世界のF5 BIG-IPで管理コンソールとトラフィック処理が分離しているのと同じ考え方です。

Gateway(Virtual Server)の準備ができました。次は、ルーティングルール(HTTPRoute)を設定します。

5.5.2 HTTPRouteでパスベースルーティングを設定する

HTTPRouteで、パスに基づいてリクエストをNginxとTaskBoard APIに振り分けるルールを定義します。

[Execution User: developer]

cat <<'EOF' > ~/k8s-applied/httproute-taskboard.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: taskboard-route
  namespace: app
spec:
  # このHTTPRouteをアタッチするGateway
  parentRefs:
    - name: taskboard-gateway
      namespace: app
  rules:
    # ルール1: /api で始まるパス → TaskBoard API Service
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: taskboard-api
          port: 8080
    # ルール2: / で始まるパス → Nginx Service(フォールバック)
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: nginx
          port: 80
EOF

ポイントを整理します。

parentRefs で、先ほど作成した taskboard-gateway にこのHTTPRouteをアタッチしています。このGatewayに届いたリクエストに対して、このルーティングルールが適用されます。

rules には2つのルールを定義しています。ルールは上から順に評価されますが、Gateway APIではマッチの「具体性」が優先されます。/api/ より具体的なので、/api/tasks へのリクエストは必ず1つ目のルール(TaskBoard API)にマッチします。/ へのアクセスや、/api に該当しないパスは2つ目のルール(Nginx)にフォールバックします。

backendRefs には、振り分け先のService名とポートを指定しています。taskboard-api Serviceの8080番ポート、nginx Serviceの80番ポートです。どちらもapp Namespace内のClusterIP Service(またはNodePort Service)を参照します。

applyしましょう。

[Execution User: developer]

kubectl apply -f ~/k8s-applied/httproute-taskboard.yaml
httproute.gateway.networking.k8s.io/taskboard-route created

HTTPRouteの状態を確認します。

[Execution User: developer]

kubectl get httproute -n app
NAME              HOSTNAMES   PARENTREFS                    AGE
taskboard-route               ["app/taskboard-gateway"]     15s

PARENTREFStaskboard-gateway が表示されていれば、HTTPRouteがGatewayに正しくアタッチされています。

5.5.3 動作確認 — curlで各パスにアクセスする

Gateway経由でTaskBoardにアクセスしてみましょう。ただし、ここでkind環境固有の事情があります。

Envoy GatewayはGateway用のServiceを LoadBalancer タイプで作成します。AWSのEKSやGCPのGKEであれば、クラウドプロバイダーがロードバランサーを自動的にプロビジョニングし、外部IPアドレスを割り当ててくれます。しかし、kindはローカルのDockerコンテナ上で動いているため、クラウドのロードバランサーは使えません。ServiceのEXTERNAL-IPは <pending> のままです。

[Execution User: developer]

kubectl get svc -n envoy-gateway-system -l gateway.envoyproxy.io/owning-gateway-name=taskboard-gateway
NAME                             TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
envoy-app-taskboard-gateway-*    LoadBalancer   10.96.x.x    <pending>     80:3xxxx/TCP   2m

kind環境では kubectl port-forward を使って、ホストマシンからGatewayのPodに直接アクセスします。これが最もシンプルな方法です。

まず、GatewayのService名を取得します。

[Execution User: developer]

export GATEWAY_SVC=$(kubectl get svc -n envoy-gateway-system \
  -l gateway.envoyproxy.io/owning-gateway-name=taskboard-gateway \
  -o jsonpath='{.items[0].metadata.name}')
echo $GATEWAY_SVC

ポートフォワードを開始します。ホストの8080番ポートをGatewayの80番ポートに転送します。

[Execution User: developer]

kubectl port-forward -n envoy-gateway-system svc/$GATEWAY_SVC 8080:80 &
Forwarding from 127.0.0.1:8080 -> 10080
Forwarding from [::1]:8080 -> 10080

& でバックグラウンド実行しています。転送先のポートが10080になっていますが、これはEnvoy Gatewayが特権ポート(80番)を内部的に非特権ポート(10080番)にマッピングしているためです。利用者側は意識する必要はありません。

本番環境(マネージドK8s)では: EKS/GKE/AKSなどのマネージドKubernetesでは、GatewayのServiceに対してクラウドのロードバランサーが自動的にプロビジョニングされ、外部IPアドレスが割り当てられます。port-forwardは不要で、そのIPアドレス(またはDNS名)に直接アクセスできます。kindでの port-forward は学習環境ならではの手順です。

では、curlでGateway経由のアクセスを確認しましょう。

まず、フロントエンド(Nginx)へのアクセスです。

[Execution User: developer]

curl -s http://localhost:8080/

Nginxのデフォルトページ(Welcome to nginx!)が返ってくれば成功です。

次に、TaskBoard APIへのアクセスです。

[Execution User: developer]

curl -s http://localhost:8080/api/tasks | head -20

タスクの一覧(JSON配列)が返ってくれば成功です。DB初期化Jobで投入した初期データが表示されるはずです。

1つの入口(localhost:8080)から、パスによってNginxとTaskBoard APIに振り分けられていることを確認できました。NodePortのようにポート番号を使い分ける必要はありません。

5.6 ルーティングの挙動を検証する

5.6.1 パスベースの振り分けを確認する(/ → Nginx、/api → API)

もう少し詳しくルーティングの挙動を検証しましょう。HTTPRouteで定義した2つのルールが、期待通りに動作していることを確認します。

[Execution User: developer]

# / → Nginx
curl -s -o /dev/null -w "Path: /\t\tHTTP Status: %{http_code}\n" http://localhost:8080/

# /api/tasks → TaskBoard API
curl -s -o /dev/null -w "Path: /api/tasks\tHTTP Status: %{http_code}\n" http://localhost:8080/api/tasks

# /index.html → Nginx(/ のPathPrefixにマッチ)
curl -s -o /dev/null -w "Path: /index.html\tHTTP Status: %{http_code}\n" http://localhost:8080/index.html
Path: /		HTTP Status: 200
Path: /api/tasks	HTTP Status: 200
Path: /index.html	HTTP Status: 200

すべて200が返っています。//index.html はNginxに、/api/tasks はTaskBoard APIに振り分けられています。HTTPRouteの PathPrefix マッチにより、/api で始まるパスはすべてTaskBoard APIへ、それ以外はNginxへルーティングされます。

レスポンスヘッダーでも確認してみましょう。

[Execution User: developer]

# Nginx のレスポンスヘッダー
curl -s -I http://localhost:8080/ 2>&1 | grep -i server

# TaskBoard API のレスポンスヘッダー
curl -s -I http://localhost:8080/api/tasks 2>&1 | grep -i server
server: nginx/1.27.x
server: Payara Micro #badassfish

server ヘッダーが異なることから、同じ入口(localhost:8080)へのアクセスがパスに応じて別々のバックエンドに届いていることがわかります。

5.6.2 存在しないパスへのアクセス — 404の挙動

HTTPRouteに定義されていないパスへのアクセスはどうなるでしょうか。試してみましょう。

[Execution User: developer]

curl -s -o /dev/null -w "HTTP Status: %{http_code}\n" http://localhost:8080/nonexistent
HTTP Status: 404

404が返りました。ただし、この404はNginxが返したものです。理由を考えてみましょう。

HTTPRouteの2つ目のルールは PathPrefix: / です。/nonexistent/ というプレフィックスを持っているため、このルールにマッチしてNginxにルーティングされます。Nginxは /nonexistent に対応するファイルを持っていないため、Nginx自身が404を返しているのです。

つまり、PathPrefix: / のルールは事実上のフォールバック(デフォルトルート)として機能しています。F5 BIG-IPでいう「default pool」の役割です。どのルールにもマッチしないリクエストはすべてNginxに到達し、Nginx側の設定に委ねられます。

HTTPRouteにマッチするルールが一切なかった場合(たとえば PathPrefix: / のルールも定義していなかった場合)、Envoy Gatewayが直接404を返します。この違いを理解しておくと、トラブルシュート時に「404を返しているのはGateway(Envoy)なのか、バックエンドなのか」を切り分けられます。

5.6.3 NodePortとの比較 — 何が良くなったか

Gateway APIを導入する前のTaskBoardのアクセス経路を振り返り、何が改善されたかを整理しましょう。

観点Before(NodePort)After(Gateway API)
アクセス方法<NodeIP>:30080 でAPIに直接アクセス<Gateway>:80/api でアクセス
入口の数Serviceごとに別々のNodePort(30080, 30081…)1つのGateway(80番ポート)に集約
パスベース振り分けできない(ポート番号でしか分けられない)HTTPRouteでパスごとにServiceを振り分け
ポート管理30000-32767の範囲でポート番号を管理する必要があるGatewayの80番ポートのみ。パスで分離
本番との親和性NodePortを本番で使うことは稀Gateway APIはマネージドK8sでもそのまま使える

これでTaskBoard APIのServiceをNodePortのままにしておく理由がなくなりました。Gateway API経由でアクセスするため、TaskBoard APIのServiceはClusterIP(クラスタ内部からのみアクセス可能)で十分です。

TaskBoard APIのServiceをClusterIPに変更しましょう。

[Execution User: developer]

cat <<'EOF' > ~/k8s-applied/taskboard-api-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: taskboard-api
  namespace: app
  labels:
    app: taskboard
    component: api
spec:
  # NodePort → ClusterIP に変更(Gateway API経由でアクセスするため)
  type: ClusterIP
  selector:
    app: taskboard
    component: api
  ports:
    - port: 8080
      targetPort: 8080
EOF

[Execution User: developer]

kubectl apply -f ~/k8s-applied/taskboard-api-service.yaml
service/taskboard-api configured

変更後もGateway経由のアクセスが引き続き機能することを確認します。

[Execution User: developer]

curl -s http://localhost:8080/api/tasks | head -5

タスクのJSON配列が返ってくれば、問題ありません。NodePort(30080番)を経由せず、Gateway API経由でのみアクセスする構成に切り替わりました。

一方、NodePort経由のアクセスはもうできなくなっています。

[Execution User: developer]

# NodePort経由(もう使えない)
kubectl get svc taskboard-api -n app
NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
taskboard-api    ClusterIP   10.96.x.x     <none>        8080/TCP   ●d

TYPEがClusterIPになり、NodePort列が消えました。外部からの入口はGateway APIに一本化されました。

port-forwardの停止: 検証が終わったら、バックグラウンドのport-forwardプロセスを停止しておきましょう。

[Execution User: developer]

# バックグラウンドのport-forwardを停止
kill %1 2>/dev/null; echo "port-forward stopped"

以降のハンズオンで再度Gatewayにアクセスする場合は、同じ kubectl port-forward コマンドを実行してください。

5.7 この回のまとめ

5.7.1 TaskBoardの現在地

今回の作業で、TaskBoardに以下が追加されました。

[app Namespace]
  Nginx (Deployment, replicas: 2) + Service (ClusterIP)
  TaskBoard API (Deployment, replicas: 2, MySQL接続版) + Service (ClusterIP) ← NodePortから変更
  Gateway (taskboard-gateway) ← 今回追加
  HTTPRoute (taskboard-route) ← 今回追加
  + ResourceQuota / LimitRange 適用済み
  + RBAC適用済み

[db Namespace]
  MySQL (StatefulSet, replicas: 1) + Headless Service + PVC
  + DB初期化Job(Completed)
  + DBバックアップCronJob(稼働中)
  + Secret(MySQL認証情報)
  + ResourceQuota / LimitRange 適用済み
  + RBAC適用済み

[monitoring Namespace]
  ログ収集DaemonSet(全Worker Nodeに配置)
  + ResourceQuota / LimitRange 適用済み
  + RBAC適用済み

[envoy-gateway-system Namespace]
  Envoy Gatewayコントローラー ← 今回追加
  Envoy Proxy データプレーン ← 今回自動作成

[クラスタ全体]
  GatewayClass (eg) ← 今回追加
  Metrics Server 稼働中
  マルチノード構成(CP 1 + Worker 3)

外部からのアクセスは、Gateway APIで / → Nginx、/api → TaskBoard API にパスベースで振り分けられる構成になりました。NodePortは撤去済みです。

5.7.2 外部アクセス設計の判断基準 — NodePort / LoadBalancer / Gateway API

Kubernetesで外部からのトラフィックを受ける方法は主に3つあります。どれを使うべきかの判断基準を整理します。

方式適するケース適さないケース
NodePort開発・検証環境での一時的なアクセス。デバッグ用途本番環境。サービスが増えるとポート管理が破綻する
LoadBalancerL4(TCP/UDP)レベルのロードバランシングで十分な場合。マネージドK8sでクラウドLBを直接使いたい場合パスベースやヘッダーベースのL7ルーティングが必要な場合
Gateway APIL7ルーティングが必要な場合。複数のServiceをパス/ホスト名で振り分ける場合。本番環境の標準L4で十分かつ極限までシンプルにしたい場合(ただしGateway APIもTCPRouteをサポート)

判断のフローは単純です。「複数のServiceをパスやホスト名で振り分けたい」ならGateway API一択です。「L4(TCP/UDP)で十分で、Service 1つだけを外部公開したい」ならLoadBalancerで済みます。「開発中に一時的にアクセスしたいだけ」ならNodePortかport-forwardで十分です。

本番環境では、ほとんどのケースでGateway APIが正解になります。1つのServiceしか公開しない場合でも、将来的にServiceが増える可能性があるなら、最初からGateway APIで設計しておくほうが拡張性があります。

5.7.3 実践編への橋渡し

今回学んだGateway APIの知識は、実践編で以下の場面で使います。

  • 実践編 第3回(詳細設計): GatewayとHTTPRouteのパラメータを設計根拠とともに決定します。リスナー設定、ルーティングルール、タイムアウト値などを「なぜこの値にしたか」を説明できるレベルで設計します
  • 実践編 第6回(ネットワーク構築と結合テスト): Gateway APIの設定を本番品質で適用し、E2E(End-to-End)テストで外部クライアント → Gateway → Service → Pod → DB の全経路の疎通を確認します
  • 実践編 第9回(障害対応): Gateway関連の障害(HTTPRoute設定ミスによるルーティング異常など)の切り分けと復旧を実践します

5.7.4 次回予告

TaskBoardの入口が整いました。しかし、まだ大きな問題が残っています。クラスタ内部では、すべてのPodがフラットに通信できる状態です。フロントエンド(Nginx)から直接MySQLに接続することも、理論的には可能です。

次回(第6回)では、NetworkPolicyを使って「誰が誰と通信できるか」を制御します。VMware NSXの分散ファイアウォールに相当する、Pod間通信の最小権限化に取り組みます。

AIコラム — Gateway APIの設計をAIに相談する

Gateway APIの設計では「パスベースで振り分けるか、ホスト名ベースで振り分けるか」の判断が必要になることがあります。たとえば、今回のTaskBoardでは /api/ のパスベースルーティングを選びましたが、規模が大きくなると api.taskboard.example.comwww.taskboard.example.com のようにホスト名ベースのほうが整理しやすい場合もあります。

こういった設計判断の壁打ちにAIを活用できます。「パスベースとホスト名ベースのルーティング、TaskBoardのような社内アプリではどちらが適切か?」と聞いてみると、それぞれのメリット・デメリットを整理してくれます。ただし、AIの回答をそのまま採用するのではなく、自分の環境(DNS設定の容易さ、証明書管理の手間、将来の拡張性)に照らして判断することが大切です。

実践編第6回では、Gateway APIの設定をAIに生成させ、読者と一緒にレビューする場面があります。AIが出力したHTTPRouteの設定が正しいかどうかを、自分の目で確認するスキルが今回の学びの延長線上にあります。