cloud

使用 Istio Gateway API 對外公開 Kubernetes 服務

透過 Istio 管理的 Gateway 與 HTTPRoute,路由公開網域。

English繁中

我用 Istio Gateway API 將公開 hostname 導向 Kubernetes Services。叢集外的 reverse proxy 終結 HTTPS,再將 HTTP 送到共用的 Gateway:

external TLS reverse proxy → Istio Gateway Service → HTTPRoute → Kubernetes Service → Pod

範例中的 example-api 對應 api.example.comexample-worker 對應 worker.example.comexample-admin 維持內部使用,不建立公開路由。

分開管理 Gateway 與 route

Gateway API 將入口設定拆成四種資源:Istio 提供的 GatewayClass、共用入口 Gateway、比對 hostname 與 path 的 HTTPRoute,以及授權跨 namespace backend 引用的 ReferenceGrant。共用基礎設施可以管理 Gateway,應用程式則管理或取得自己的 routes。

固定 Gateway Service 的 NodePort

外部 reverse proxy 需要固定的轉送位置。我用 ConfigMap 指定 Gateway Service 的 NodePort:

apiVersion: v1
kind: ConfigMap
metadata:
  name: example-istio-gateway-options
  namespace: istio-ingress
data:
  service: |
    spec:
      type: NodePort
      ports:
        - name: status-port
          port: 15021
          protocol: TCP
          targetPort: 15021
        - name: http
          port: 80
          protocol: TCP
          targetPort: 80
          nodePort: 32080

Gateway 透過 infrastructure.parametersRef 引用這份設定:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: example-istio-gateway
  namespace: istio-ingress
  annotations:
    networking.istio.io/service-type: NodePort
spec:
  gatewayClassName: istio
  infrastructure:
    parametersRef:
      group: ""
      kind: ConfigMap
      name: example-istio-gateway-options
  listeners:
    - name: http
      hostname: "*.example.com"
      port: 80
      protocol: HTTP
      allowedRoutes:
        namespaces:
          from: All

外部 reverse proxy 終結 TLS,再將 HTTP 轉送至 NodePort 32080。Listener 的 allowedRoutes 決定哪些 namespace 的 routes 可以附掛;此例使用 All

用 HTTPRoute 指向 backend Service

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: example-api
  namespace: istio-ingress
spec:
  parentRefs:
    - name: example-istio-gateway
  hostnames:
    - api.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: example-api
          namespace: example-api
          port: 3000

HTTPRoute 位於 istio-ingress,backend Service 位於 example-api,因此還需要在 backend namespace 建立 ReferenceGrant:

apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-istio-ingress-to-example-api
  namespace: example-api
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      namespace: istio-ingress
  to:
    - group: ""
      kind: Service
      name: example-api

這份 grant 允許指定 route namespace 引用名為 example-api 的 Service。它和 listener 的 allowedRoutes 管理不同關係:前者處理 backend 引用,後者處理 route 附掛。完整說明見 Gateway API security model

加入另一個 hostname

Worker UI 使用相同 Gateway,新增自己的 HTTPRoute:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: example-worker
  namespace: istio-ingress
spec:
  parentRefs:
    - name: example-istio-gateway
  hostnames:
    - worker.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: example-worker
          namespace: example-worker
          port: 8000

Worker namespace 也要授權這條 backend 引用:

apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-istio-ingress-to-example-worker
  namespace: example-worker
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      namespace: istio-ingress
  to:
    - group: ""
      kind: Service
      name: example-worker

新增服務時不用另外建立 Gateway。example-admin 雖然也在叢集內、使用 Vault/External Secrets,這裡仍不為它加入公開路由。

與 ambient waypoint 的分工

需要 ambient mesh 的 namespace 使用以下 label:

istio.io/dataplane-mode: ambient

需要 service-scoped L7 處理時,再建立 waypoint:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: example-api-waypoint
  namespace: example-api
  labels:
    istio.io/waypoint-for: service
spec:
  gatewayClassName: istio-waypoint
  listeners:
    - name: mesh
      port: 15008
      protocol: HBONE

建立 waypoint 後,還要在目標 Service 加上 istio.io/use-waypoint: example-api-waypoint;完整 Service 範例見 ambient waypoint 文章

Ingress Gateway 處理進入叢集的流量。istio.io/use-waypoint 選用的是 mesh 流量的 waypoint,ingress 流量預設會略過它。若公開請求也需要經過 waypoint,Istio 1.25 起支援在目的 Service 或 Namespace 加上 istio.io/ingress-use-waypoint: "true",並在 istiod 啟用 ENABLE_INGRESS_WAYPOINT_ROUTING=true。依賴 waypoint policy 或 telemetry 前,應確認這兩項設定;參考 ingress gateways and waypoints

辨認外部 HTTPS 目的地

對 Pods 呼叫的外部 API 加入 ServiceEntry,讓 Istio 與 Kiali 能辨認目的地:

apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: example-external-api
  namespace: example-api
spec:
  hosts:
    - api.vendor.example
  location: MESH_EXTERNAL
  ports:
    - number: 443
      name: tls
      protocol: TLS
  resolution: DNS

我使用明確的 FQDN,方便審查與對照流量,避免用 wildcard 掩蓋實際存取哪些 hosts。

逐層驗證

先查 Gateway 與產生的 Service,再查 routes 和 grants:

kubectl -n istio-ingress get gateway,svc,deploy
kubectl -n istio-ingress get httproute
kubectl -n example-api get referencegrant
kubectl -n example-worker get referencegrant

從叢集外測試公開入口:

curl https://api.example.com/health
curl https://worker.example.com/

失敗時依序檢查 DNS、reverse proxy 到 NodePort 的連線、listener hostname、HTTPRoute 的 Accepted 狀態、ReferenceGrant,以及 backend Service 的 ready endpoints。