コンテンツにスキップ

【Envoy Gateway】Gateway Namespace Mode を試してみた

本記事では、Envoy Gateway における"Gateway Namespace Mode"について、公式ドキュメントの例を基に紹介します。

Envoy Gateway の標準のデプロイモードでは、Envoy Proxy に関連するリソース(Deployment, Service 等)は、Envoy Gateway コントローラーと同じ Namespace(通常 envoy-gateway-system)に作成されます。

これに対し、Gateway Namespace Modeは、これらのデータプレーンリソースを各 Gateway リソースが定義された Namespace に配置することにより、リソース分離とマルチテナンシーの強化を実現する機能です。

本記事の内容:

  • Gateway Namespace Mode の概要と利点
  • 標準デプロイモードとの相違点
  • Gateway Namespace Mode の設定手順
  • 実際のデプロイ例を通じた動作検証

留意事項: Gateway Namespace Mode は、本記事執筆時点(v1.4.0)においてアルファ版の機能です。実稼働環境での利用は、ベータ版以降のリリースを待つことが推奨されます。最新情報やフィードバックについては、公式 GitHub issues をご参照ください。また、現時点では Merged Gateways との併用はサポートされていません。

Gateway Namespace Mode の概要

Gateway Namespace Mode の主要な特徴は次の通りです。

  • データプレーンリソースの分離: Envoy Proxy の Deployment, Service, ServiceAccount といったデータプレーンリソースが、Gateway リソースと同一の Namespace に作成されます。これにより、テナントやチーム単位でのリソース分離が明確になります。
  • 認証方式の変更:
    • 標準モードでは、インフラストラクチャとコントロールプレーン間で mTLS (相互 TLS 認証) が使用されます。
    • Gateway Namespace Mode では、サーバーサイド TLS および JWT (JSON Web Token) 検証へと移行しています。
    • 具体的には、projected service account JWT token を利用し、有効期間が短く、対象オーディエンスが限定されたトークンが Pod に自動的にマウントされます。
    • JWT 検証により、認可されたプロキシのみが xDS サーバーに接続可能であることを保証します。
    • Gateway の Namespace で実行される Pod には CA 証明書のみが利用可能となり、クライアント証明書はマウントされません。Envoy Proxy は引き続き CA 証明書を使用してサーバー証明書を検証します。

設定手順

Gateway Namespace Mode を有効化するには、Envoy Gateway の Helm チャートの値を変更します。

  1. Helm Values の設定: provider.kubernetes.deploy.type フィールドを GatewayNamespace に設定します。
    1
    2
    3
    4
    5
    6
    7
    config:
      envoyGateway:
        provider:
          type: Kubernetes
          kubernetes:
            deploy:
              type: GatewayNamespace
    
  2. Helm コマンドによるインストール例: 次のコマンドでも Gateway Namespace Mode を有効にしてインストール可能です。
    1
    2
    3
    4
    helm install \
      --set config.envoyGateway.provider.kubernetes.deploy.type=GatewayNamespace \
      eg oci://docker.io/envoyproxy/gateway-helm \
      --version v1.4.0 -n envoy-gateway-system --create-namespace
    

RBAC 設定

Gateway Namespace Mode を使用する場合、Envoy Gateway は異なる Namespace 間でリソースを作成・管理するために追加の RBAC (Role-Based Access Control) 権限を必要とします。これらの RBAC リソース (ClusterRole, ClusterRoleBinding) は、Gateway Namespace Mode を有効にして Helm チャートをインストールする際に自動的に作成されます。

例えば、次のような ClusterRole が作成され、ServiceAccount, Service, Deployment 等のリソースに対する create, get, delete などの権限が付与されます。

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: gateway-helm-cluster-infra-manager
rules:
  - apiGroups: [""]
    resources: ["serviceaccounts", "services", "configmaps"]
    verbs: ["create", "get", "delete", "deletecollection", "patch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "daemonsets"]
    verbs: ["create", "get", "delete", "deletecollection", "patch"]
  - apiGroups: ["autoscaling", "policy"]
    resources: ["horizontalpodautoscalers", "poddisruptionbudgets"]
    verbs: ["create", "get", "delete", "deletecollection", "patch"]
  - apiGroups: ["authentication.k8s.io"]
    resources: ["tokenreviews"]
    verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: gateway-helm-cluster-infra-manager
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: "gateway-helm-cluster-infra-manager"
subjects:
  - kind: ServiceAccount
    name: "envoy-gateway"
    namespace: "envoy-gateway-system"

特定の Namespace のみを監視対象とする設定 (EnvoyGateway.provider.kubernetes.watch.namespacesEnvoyGateway.provider.kubernetes.watch.namespaceSelector) を行っている場合、Envoy Gateway は指定された Namespace の Gateway API リソースのみを監視し、インフラ管理に必要な Role をそれらの Namespace に作成します。

公式の例を試す

公式ドキュメントに記載されている例を参考に、Gateway Namespace Mode の動作を検証します。この例では、team-ateam-b という 2 つの異なる Namespace に Gateway をデプロイします。

テスト用 Namespace の作成

まず、テスト用の Namespace を作成します。

kubectl create namespace team-a
kubectl create namespace team-b

サンプルマニフェストの適用

次に、公式ドキュメントで提供されているサンプルマニフェストを適用します。これにより、team-a および team-b の各 Namespace に、バックエンドの Deployment、Gateway、そしてそれぞれの HTTPRoute が作成されます。

kubectl apply -f https://raw.githubusercontent.com/envoyproxy/gateway/refs/heads/release/v1.4/examples/kubernetes/gateway-namespace-mode.yaml

リソースの確認

各リソースが正しくデプロイされたかを確認します。

  • Gateway の確認:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    kubectl get gateways -n team-a
    # 出力例:
    # NAME        CLASS   ADDRESS        PROGRAMMED   AGE
    # gateway-a   eg      172.18.0.200   True         67s
    
    kubectl get gateways -n team-b
    # 出力例:
    # NAME        CLASS   ADDRESS        PROGRAMMED   AGE
    # gateway-b   eg      172.18.0.201   True         67s
    

    PROGRAMMEDTrue であることを確認します。

  • HTTPRoute の確認:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    kubectl get httproute -n team-a
    # 出力例:
    # NAME           HOSTNAMES            AGE
    # team-a-route   ["www.team-a.com"]   67s
    
    kubectl get httproute -n team-b
    # 出力例:
    # NAME           HOSTNAMES            AGE
    # team-b-route   ["www.team-b.com"]   67s
    
  • Pod の確認: Gateway Namespace Mode の特徴として、Envoy Proxy の Pod が各 Gateway の Namespace (team-a, team-b) に作成されます。

    kubectl get pods -n team-a
    # 出力例:
    # NAME                                              READY   STATUS    RESTARTS   AGE
    # envoy-team-a-gateway-a-b65c6264-d56f5d989-6dv5s   2/2     Running   0          65s
    # team-a-backend-6f786fb76f-nx26p                   1/1     Running   0          65s
    
    kubectl get pods -n team-b
    # 出力例:
    # NAME                                               READY   STATUS    RESTARTS   AGE
    # envoy-team-b-gateway-b-0ac91f5a-74f445884f-95pl8   2/2     Running   0          87s
    # team-b-backend-966b5f47c-zxngl                     1/1     Running   0          87s
    

    envoy-team-a-gateway-a-...envoy-team-b-gateway-b-... といった名称の Pod がそれぞれの Namespace に存在し、READY 2/2 となっていることを確認します。

  • Service の確認: 同様に、Envoy Proxy の Service も各 Namespace に作成されます。

    kubectl get services -n team-a
    # 出力例:
    # NAME                              TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)          AGE
    # envoy-team-a-gateway-a-b65c6264   LoadBalancer   10.96.191.198   172.18.0.200   8080:30999/TCP   3m2s
    # team-a-backend                    ClusterIP      10.96.92.226    <none>         3000/TCP         3m2s
    
    kubectl get services -n team-b
    # 出力例:
    # NAME                              TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)          AGE
    # envoy-team-b-gateway-b-0ac91f5a   LoadBalancer   10.96.144.13   172.18.0.201   8081:31683/TCP   3m43s
    # team-b-backend                    ClusterIP      10.96.26.162   <none>         3000/TCP         3m43s
    

動作テスト

最後に、実際にリクエストを送信し、ルーティングが機能しているかを確認します。

  • 外部 IP の取得: まず、各 Gateway Service の外部 IP アドレスを取得します。

    export GATEWAY_HOST_A=$(kubectl get gateway/gateway-a -n team-a -o jsonpath='{.status.addresses[0].value}')
    export GATEWAY_HOST_B=$(kubectl get gateway/gateway-b -n team-b -o jsonpath='{.status.addresses[0].value}')
    
  • team-a へのリクエスト: team-a の Gateway (ポート 8080) を経由して、www.team-a.com へのリクエストを送信します。

    curl --header "Host: www.team-a.com" http://$GATEWAY_HOST_A:8080/example
    

    期待されるレスポンス例:

    {
      "path": "/example",
      "host": "www.team-a.com",
      "method": "GET",
      "proto": "HTTP/1.1",
      "headers": {
        "Accept": ["*/*"],
        "User-Agent": ["curl/8.7.1"],
        "X-Envoy-External-Address": ["172.18.0.3"],
        "X-Forwarded-For": ["172.18.0.3"],
        "X-Forwarded-Proto": ["http"],
        "X-Request-Id": ["52f08a5c-7e07-43b7-bd23-44693c60fc0c"]
      },
      "namespace": "team-a",
      "ingress": "",
      "service": "",
      "pod": "team-a-backend-6f786fb76f-nx26p"
    }
    

    レスポンス内の "namespace": "team-a" および "pod": "team-a-backend-..." から、team-a のバックエンドにルーティングされたことが確認できます。

  • team-b へのリクエスト: team-b の Gateway (ポート 8081) を経由して、www.team-b.com へのリクエストを送信します。
    curl --header "Host: www.team-b.com" http://$GATEWAY_HOST_B:8081/example
    
    期待されるレスポンス例:
    {
      "path": "/example",
      "host": "www.team-b.com",
      "method": "GET",
      "proto": "HTTP/1.1",
      "headers": {
        "Accept": ["*/*"],
        "User-Agent": ["curl/8.7.1"],
        "X-Envoy-External-Address": ["172.18.0.3"],
        "X-Forwarded-For": ["172.18.0.3"],
        "X-Forwarded-Proto": ["http"],
        "X-Request-Id": ["62a06bd7-4754-475b-854a-dca3fc159e93"]
      },
      "namespace": "team-b",
      "ingress": "",
      "service": "",
      "pod": "team-b-backend-966b5f47c-d6jwj"
    }
    
    同様に、"namespace": "team-b" から team-b のバックエンドにルーティングされたことが確認できます。

以上の手順により、Gateway Namespace Mode が正しく機能し、各 Namespace の Gateway がそれぞれのデータプレーンリソースを保持し、独立して動作していることが確認できました。

ClusterIP での動作確認

Gateway Namespace Mode では、Envoy Proxy の Service が各 Gateway の Namespace に作成されますが、ClusterIP タイプの Service でも動かせます。 デフォルトの Service タイプは LoadBalancer となっており、外部 IP アドレスが割り当てられます。 ClusterIP にすることで外部 IP を持たない Service となり、Kubernetes クラスタ内での通信に限定されます。

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
  name: example-envoy-proxy
  namespace: team-a
spec:
  provider:
    kubernetes:
      envoyService:
        name: my-gateway
        type: ClusterIP # 公開しない場合は ClusterIP を指定
    type: Kubernetes
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: eg
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
  parametersRef:
    group: gateway.envoyproxy.io
    kind: EnvoyProxy # GatewayClassにEnvoyProxyを紐付ける
    name: example-envoy-proxy
    namespace: team-a
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: team-a-backend
  namespace: team-a
---
apiVersion: v1
kind: Service
metadata:
  name: team-a-backend
  namespace: team-a
  labels:
    app: team-a-backend
    service: team-a-backend
spec:
  ports:
    - name: http
      port: 3000
      targetPort: 3000
  selector:
    app: team-a-backend
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: team-a-backend
  namespace: team-a
spec:
  replicas: 1
  selector:
    matchLabels:
      app: team-a-backend
      version: v1
  template:
    metadata:
      labels:
        app: team-a-backend
        version: v1
    spec:
      serviceAccountName: team-a-backend
      containers:
        - image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e
          imagePullPolicy: IfNotPresent
          name: team-a-backend
          ports:
            - containerPort: 3000
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: team-b-backend
  namespace: team-b
---
apiVersion: v1
kind: Service
metadata:
  name: team-b-backend
  namespace: team-b
  labels:
    app: team-b-backend
    service: team-b-backend
spec:
  ports:
    - name: http
      port: 3000
      targetPort: 3000
  selector:
    app: team-b-backend
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: team-b-backend
  namespace: team-b
spec:
  replicas: 1
  selector:
    matchLabels:
      app: team-b-backend
      version: v1
  template:
    metadata:
      labels:
        app: team-b-backend
        version: v1
    spec:
      serviceAccountName: team-b-backend
      containers:
        - image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e
          imagePullPolicy: IfNotPresent
          name: team-b-backend
          ports:
            - containerPort: 3000
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gateway-a
  namespace: team-a
spec:
  gatewayClassName: eg
  listeners:
    - allowedRoutes:
        namespaces:
          from: Same
      name: http
      port: 8080
      protocol: HTTP
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gateway-b
  namespace: team-b
spec:
  gatewayClassName: eg
  listeners:
    - allowedRoutes:
        namespaces:
          from: Same
      name: http
      port: 8081
      protocol: HTTP
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: team-a-route
  namespace: team-a
spec:
  parentRefs:
    - name: gateway-a
  hostnames:
    - "www.team-a.com"
  rules:
    - backendRefs:
        - group: ""
          kind: Service
          name: team-a-backend
          port: 3000
          weight: 1
      matches:
        - path:
            type: PathPrefix
            value: /example
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: team-b-route
  namespace: team-b
spec:
  parentRefs:
    - name: gateway-b
  hostnames:
    - "www.team-b.com"
  rules:
    - backendRefs:
        - group: ""
          kind: Service
          name: team-b-backend
          port: 3000
          weight: 1
      matches:
        - path:
            type: PathPrefix
            value: /example

先ほど検証で使用した公式のマニフェストを元に、新しく EnvoyProxy リソースを作成し、GatewayClass に EnvoyProxy を紐付けています。 この EnvoyProxy リソースを作成することで、Envoy Proxy の Service が ClusterIP タイプで作成されます。 ここでは、複数の namespace を跨いで 1 つの EnvoyProxy リソースを使っていますが、各 namespace 毎に EnvoyProxy リソースを作成可能です。

それぞれの Gateway に対してリクエストを送り、通信ができることを確認します。

1
2
3
4
kubectl run curlpod --rm -i -t --restart=Never --image=mirror.gcr.io/curlimages/curl:latest -- sh

curl my-gateway.team-a.svc.cluster.local:8080/example curl --header "Host: www.team-a.com"
curl my-gateway.team-b.svc.cluster.local:8081/example curl --header "Host: www.team-b.com"

ここでは実行結果の記述を省略しますが、上記のコマンドを実行すると公式例と同様のレスポンスが得られます。

結論

Envoy Gateway の Gateway Namespace Mode は、データプレーンリソースを Gateway ごとに分離することにより、マルチテナンシー環境における分離性と管理性を向上させる機能です。認証メカニズムも JWT ベースに移行し、セキュリティ面も考慮されています。

現時点ではアルファ版ですが、今後の発展が期待される機能の 1 つです。実稼働環境での利用に際しては慎重な検討を行い、公式ドキュメントや GitHub issue で最新情報を確認することを推奨します。

参照資料: