Istio 使用 Gateway API 实现流量管理

开源
Gateway API 最初设计用于管理从集群外部客户端到集群内部服务的流量(入口或北/南情况)。随着时间的推移,服务网格用户的兴趣促使 GAMMA(Gateway API for Service Mesh)计划的创建,以定义 Gateway API 如何用于同一集群内的服务间或东/西流量。

Gateway API 是由 SIG-NETWORK 社区管理的开源项目,项目地址:https://gateway-api.sigs.k8s.io/。主要原因是 Ingress 资源对象不能很好的满足网络需求,很多场景下 Ingress 控制器都需要通过定义 annotations 或者 crd 来进行功能扩展,这对于使用标准和支持是非常不利的,新推出的 Gateway API 旨在通过可扩展的面向角色的接口来增强服务网络。

Gateway API 是 Kubernetes 中的一个 API 资源集合,包括 GatewayClass、Gateway、HTTPRoute、TCPRoute、Service 等,这些资源共同为各种网络用例构建模型。

Gateway API

Gateway API 最初设计用于管理从集群外部客户端到集群内部服务的流量(入口或北/南情况)。随着时间的推移,服务网格用户的兴趣促使 GAMMA(Gateway API for Service Mesh)计划的创建,以定义 Gateway API 如何用于同一集群内的服务间或东/西流量。

Gateway API 的改进比当前的 Ingress 资源对象有很多更好的设计:

  • 面向角色 - Gateway 由各种 API 资源组成,这些资源根据使用和配置 Kubernetes 服务网络的角色进行建模。
  • 通用性 - 和 Ingress 一样是一个具有众多实现的通用规范,Gateway API 是一个被设计成由许多实现支持的规范标准。
  • 更具表现力 - Gateway API 资源支持基于 Header 头的匹配、流量权重等核心功能,这些功能在 Ingress 中只能通过自定义注解才能实现。
  • 可扩展性 - Gateway API 允许自定义资源链接到 API 的各个层,这就允许在 API 结构的适当位置进行更精细的定制。

还有一些其他值得关注的功能:

  • GatewayClasses - GatewayClasses 将负载均衡实现的类型形式化,这些类使用户可以很容易了解到通过 Kubernetes 资源可以获得什么样的能力。
  • 共享网关和跨命名空间支持 - 它们允许共享负载均衡器和 VIP,允许独立的路由资源绑定到同一个网关,这使得团队可以安全地共享(包括跨命名空间)基础设施,而不需要直接协调。
  • 规范化路由和后端 - Gateway API 支持类型化的路由资源和不同类型的后端,这使得 API 可以灵活地支持各种协议(如 HTTP 和 gRPC)和各种后端服务(如 Kubernetes Service、存储桶或函数)。
  • 服务网格支持 - Gateway API 支持将路由资源与服务资源关联,以配置服务网格以及入口控制器。

面向角色设计

无论是道路、电力、数据中心还是 Kubernetes 集群,基础设施都是为了共享而建的,然而共享基础设施提供了一个共同的挑战,那就是如何为基础设施用户提供灵活性的同时还能被所有者控制。

Gateway API 通过对 Kubernetes 服务网络进行面向角色的设计来实现这一目标,平衡了灵活性和集中控制。它允许共享的网络基础设施(硬件负载均衡器、云网络、集群托管的代理等)被许多不同的团队使用,所有这些都受到集群运维设置的各种策略和约束。

gateway api demo

一个集群运维人员创建了一个基于 GatewayClass 的 Gateway 资源,这个 Gateway 配置了它所代表的基础网络资源,集群运维和特定的团队必须沟通什么可以附加到这个 Gateway 上来暴露他们的应用。 集中的策略,如 TLS,可以由集群运维在 Gateway 上强制执行,同时,Store 和 Site 应用在他们自己的命名空间中运行,但将他们的路由附加到相同的共享网关上,允许他们独立控制他们的路由逻辑。

这种关注点分离的设计可以使不同的团队能够管理他们自己的流量,同时将集中的策略和控制留给集群运维。

概念

在整个 Gateway API 中涉及到 3 个角色:基础设施提供商、集群管理员、应用开发人员,在某些场景下可能还会涉及到应用管理员等角色。Gateway API 中定义了 3 种主要的资源模型:GatewayClass、Gateway、Route。

GatewayClass

GatewayClass 定义了一组共享相同配置和动作的网关。每个 GatewayClass 由一个控制器处理,是一个集群范围的资源,必须至少有一个 GatewayClass 被定义。

这与 Ingress 的 IngressClass 类似,在 Ingress v1beta1 版本中,与 GatewayClass 类似的是 ingress-class 注解,而在 Ingress V1 版本中,最接近的就是 IngressClass 资源对象。

Gateway

Gateway 网关描述了如何将流量转化为集群内的服务,也就是说,它定义了一个请求,要求将流量从不了解 Kubernetes 的地方转换到集群内的服务。例如,由云端负载均衡器、集群内代理或外部硬件负载均衡器发送到 Kubernetes 服务的流量。

它定义了对特定负载均衡器配置的请求,该配置实现了 GatewayClass 的配置和行为规范,该资源可以由管理员直接创建,也可以由处理 GatewayClass 的控制器创建。

Gateway 可以附加到一个或多个路由引用上,这些路由引用的作用是将流量的一个子集导向特定的服务。

Route 资源

路由资源定义了特定的规则,用于将请求从网关映射到 Kubernetes 服务。从 v1alpha2 版本开始,API 中包含四种 Route 路由资源类型。

HTTPRoute

HTTPRoute 是用于 HTTP 或 HTTPS 连接,适用于我们想要检查 HTTP 请求并使用 HTTP 请求进行路由或修改的场景,比如使用 HTTP Headers 头进行路由,或在请求过程中对它们进行修改。

TLSRoute

TLSRoute 用于 TLS 连接,通过 SNI 进行区分,它适用于希望使用 SNI 作为主要路由方法的地方,并且对 HTTP 等更高级别协议的属性不感兴趣,连接的字节流不经任何检查就被代理到后端。

TCPRoute 和 UDPRoute

TCPRoute(和 UDPRoute)旨在用于将一个或多个端口映射到单个后端。在这种情况下,没有可以用来选择同一端口的不同后端的判别器,所以每个 TCPRoute 在监听器上需要一个不同的端口。你可以使用 TLS,在这种情况下,未加密的字节流会被传递到后端,当然也可以不使用 TLS,这样加密的字节流将传递到后端。

GRPCRoute

GRPCRoute 用于路由 gRPC 流量,支持 GRPCRoute 的网关必须支持 HTTP/2,无需从 HTTP/1 进行初始升级,因此可以确保 gRPC 流量正常进行。

组合

GatewayClass、Gateway、xRoute 和 Service 的组合定义了一个可实施的负载均衡器,下图说明了不同资源之间的关系:

组合关系

使用反向代理实现的网关的典型客户端/网关 API 请求流程如下所示:

  • 客户端向 http://foo.example.com 发出请求。
  • DNS 将域名解析为 Gateway 网关地址。
  • 反向代理在监听器上接收请求,并使用 Host Header 来匹配 HTTPRoute。
  • (可选)反向代理可以根据 HTTPRoute 的匹配规则进行路由。
  • (可选)反向代理可以根据 HTTPRoute 的过滤规则修改请求,即添加或删除 headers。
  • 最后,反向代理根据 HTTPRoute 的 forwardTo 规则,将请求转发给集群中的一个或多个对象,即服务。

与 Istio API 的区别

我们这里主要是讲解 Gateway API 在服务网格中的使用,首先我们先了解下 Gateway API 与 Istio API 的区别。

Gateway API 与 Istio API(如 Gateway 和 VirtualService)有很多相似之处。主资源使用相同的 Gateway 名称,并且这些资源服务于相类似的目标。

新的 Gateway API 致力于从 Kubernetes 的各种 Ingress 实现(包括 Istio)中吸取经验,以构建标准化的,独立于供应商的 API。这些 API 通常与 Istio Gateway 和 VirtualService 具有相同的用途,但依然有一些不同的地方:

  • Istio API 中的 Gateway 仅配置已部署的现有网关 Deployment/Service,而在 Gateway API 中的 Gateway 资源不仅配置也会部署网关。
  • 在 Istio VirtualService 中,所有协议都在单一的资源中配置,而在 Gateway API 中,每种协议类型都有自己的资源,例如 HTTPRoute 和 TCPRoute。
  • 虽然 Gateway API 提供了大量丰富的路由功能,但它还没有全部涵盖 Istio 的全部特性。

实现

接下来我们就来了解下如何在 Istio z 中使用 Gateway API。默认情况下 Kubernetes 集群中不会安装 Gateway API,首先我们需要安装 Gateway API CRD:

$ kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
customresourcedefinition.apiextensions.k8s.io/gatewayclasses.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/gateways.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/httproutes.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/referencegrants.gateway.networking.k8s.io created

standard-install.yaml 包括所有已升级为 GA 或 Beta 的资源,包括 GatewayClass、Gateway、HTTPRoute 和 ReferenceGrant,由于 Istio 已经对 Gateway API 提供了支持,所以现在我们就可以直接使用了。比如现在就会自动创建一个 istio 的 GatewayClass 资源对象,如下所示(另外还有一个名为 istio-remote):

$ kubectl get gatewayclass
NAME           CONTROLLER                    ACCEPTED   AGE
istio          istio.io/gateway-controller   True       106s
istio-remote   istio.io/unmanaged-gateway    True       106s
$ kubectl get gatewayclass istio -oyaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: istio
spec:
  controllerName: istio.io/gateway-controller
  description: The default Istio GatewayClass

因为大部分场景下在一个 Kubernetes 集群中只会有一个 Istio 集群,所以我们可以直接使用默认的 istio 这个 GatewayClass,如果你有多个 Istio 集群,那么你可以创建多个 GatewayClass 来区分不同的集群。

比如接下来我们来尝试将 httpbin 应用使用 Gateway API 暴露到外部,首先我们需要部署一个 httpbin 应用:

kubectl apply -f samples/httpbin/httpbin.yaml

然后接下来同样我们需要部署一个 Gateway 资源对象,用于将流量从外部负载均衡器转发到集群内的服务,如下所示:

# default-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gateway
  namespace: istio-ingress # 网关资源对象所在的命名空间
spec:
  gatewayClassName: istio # 使用默认的 istio GatewayClass
  listeners: # 监听器
    - name: default
      hostname: "*.example.com"
      port: 80
      protocol: HTTP
      allowedRoutes: # 允许的路由
        namespaces:
          from: All # 允许所有命名空间

Gateway 代表了逻辑负载均衡器的实例化,它是根据一个 istio 这个 GatewayClass 进行模板化的,网关在 80 端口上监听 HTTP 流量,这个特定的 GatewayClass 在部署后会自动分配一个 IP 地址,该地址会显示在 Gateway.status 中。

需要注意的是这里我们声明使用的命名空间为 istio-ingress, 这是因为 Istio Gateway 可以直接使用 istio ingressgateway 的 Deployment,而这个 Deployment 默认是部署在 istio-system 命名空间中的,我们这里单独将 Gateway 资源对象创建在 istio-ingress 命名空间中,那么就会自动在这个命名空间中部署一个网关控制器,用于区分默认的 istio ingressgateway。

所以我们需要在 istio-ingress 命名空间中创建这个 Gateway 资源对象,这样才能让 istio ingressgateway 通过 Gateway 资源对象来获取配置。

kubectl create namespace istio-ingress

然后直接应用这个资源对象即可:

$ kubectl apply -f default-gateway.yaml
$ kubectl get gateway -n istio-ingress
NAME      CLASS   ADDRESS   PROGRAMMED   AGE
gateway   istio             False        8m23s
$ kubectl get deploy -n istio-ingress
NAME            READY   UP-TO-DATE   AVAILABLE   AGE
gateway-istio   1/1     1            1           8m35s
$ kubectl get svc -n istio-ingress
NAME            TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                        AGE
gateway-istio   LoadBalancer   10.100.103.86   <pending>     15021:31509/TCP,80:32530/TCP   9m38s

我们也可以去对比下上面生成的 gateway-istio 和 Istio 默认的 istio-ingressgateway 的 Deployment,他们的配置几乎是一样的。

接下来我们就需要去创建一个路由规则了,也就是想要如何访问我们的 httpbin 应用,类似于 VirtualService,我们可以使用 HTTPRoute 资源对象来定义这个路由规则,如下所示:

# httpbin-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: httpbin
  namespace: default
spec:
  parentRefs: # 引用定义的 Gateway 对象
    - name: gateway
      namespace: istio-ingress
  hostnames: ["httpbin.example.com"] # 域名
  rules: # 具体的路由规则
    - matches:
        - path:
            type: PathPrefix
            value: /get # 匹配 /get 的请求
      backendRefs: # 引用的后端服务
        - name: httpbin
          port: 8000

在上面的 HTTPRoute 对象中我们通过 parentRefs 字段指定要连接到的网关,只要网关允许这种连接,这将允许路由接收来自父网关的流量,在 backendRefs 中定义将要发送流量的后端。但是需要注意我们这里只定义了匹配 /get 这个路径的请求,然后将要访问的域名通过 hostnames 来定义。

同样直接应用该资源对象即可:

$ kubectl apply -f httpbin-route.yaml
$ kubectl get httproute
NAME      HOSTNAMES                 AGE
httpbin   ["httpbin.example.com"]   5s

然后我们就可以通过 httpbin.example.com 来访问 httpbin 应用了:

# $ export INGRESS_HOST=$(kubectl get gateways.gateway.networking.k8s.io gateway -n istio-ingress -ojsnotallow='{.status.addresses[0].value}')
export GATEWAY_URL=$(kubectl get po -l istio.io/gateway-name=gateway -n istio-ingress -o 'jsnotallow={.items[0].status.hostIP}'):$(kubectl get svc gateway-istio -n istio-ingress -o 'jsnotallow={.spec.ports[?(@.name=="default")].nodePort}')

如果你的集群可以正常使用 LoadBalancer,那么 Gateway 控制器在部署后会自动分配一个 IP 地址,该地址会显示在 Gateway.status 中。我们这里暂不支持,所以还是可以通过 NodePort 方式来进行访问。然后可以使用 curl 访问 httpbin 服务:

$ curl -s -HHost:httpbin.example.com "http://$GATEWAY_URL/get"
HTTP/1.1 200 OK
server: istio-envoy
date: Mon, 18 Dec 2023 07:29:11 GMT
content-type: application/json
content-length: 494
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 2

请注意,使用 -H 标志可以将 Host HTTP 标头设置为 httpbin.example.com。这一步是必需的,因为 HTTPRoute 已配置为处理 httpbin.example.com 的请求,但是在测试环境中,该主机没有 DNS 绑定,只是将请求发送到入口 IP。

访问其他没有被显式暴露的 URL 时,正常就会看到 HTTP 404 错误:

$ curl -s -I -HHost:httpbin.example.com "http://$INGRESS_HOST/headers"
HTTP/1.1 404 Not Found
date: Mon, 18 Dec 2023 07:31:51 GMT
server: istio-envoy
transfer-encoding: chunked

同样我们也可以查看下 gateway-istio 的日志,可以看到类似于下面的日志:

$ kubectl -n istio-ingress logs -f gateway-istio-7474cd4d9b-8dw2k
# ......
2023-12-18T07:04:33.164968Z     info    cache   returned workload trust anchor from cache       ttl=23h59m59.835033638s
2023-12-18T07:04:33.621616Z     info    Readiness succeeded in 813.201909ms
2023-12-18T07:04:33.621946Z     info    Envoy proxy is ready
[2023-12-18T07:29:11.177Z] "HEAD /get HTTP/1.1" 200 - via_upstream - "-" 0 0 2 2 "10.244.1.1" "curl/7.29.0" "68f51e89-8125-4d9e-be36-e0cdc6e6ead8" "httpbin.example.com" "10.244.1.131:80" outbound|8000||httpbin.default.svc.cluster.local 10.244.1.132:46066 10.244.1.132:80 10.244.1.1:55626 - default.httpbin.0
[2023-12-18T07:31:52.051Z] "HEAD /headers HTTP/1.1" 404 NR route_not_found - "-" 0 0 0 - "10.244.1.1" "curl/7.29.0" "a9a1002f-d65c-40d8-861d-ec99d4a4a442" "httpbin.example.com" "-" - - 10.244.1.132:80 10.244.1.1:53583 - -

证明我们的路由规则已经生效了。

同样如果我们想要能够正常访问刀 /headers 路由,那么我们可以更新下 HTTPRoute 对象:

# httpbin-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: httpbin
  namespace: default
spec:
  parentRefs: # 引用定义的 Gateway 对象
    - name: gateway
      namespace: istio-ingress
  hostnames: ["httpbin.example.com"] # 域名
  rules: # 具体的路由规则
    - matches:
        - path:
            type: PathPrefix
            value: /get # 匹配 /get 的请求
        - path:
            type: PathPrefix
            value: /headers # 匹配 /headers 的请求
      filters:
        - type: RequestHeaderModifier # 添加一个修改请求头的过滤器
          requestHeaderModifier:
            add: # 添加一个标头
              - name: my-added-header
                value: added-value
      backendRefs: # 引用的后端服务
        - name: httpbin
          port: 8000

我们除了在 rules 中添加了一个匹配 /headers 的规则外,还添加了一个 RequestHeaderModifier 过滤器,用于添加一个 Header 头信息,更新这个资源对象后再次访问 /headers,注意到 My-Added-Header 标头已被添加到请求中了:

$ curl -s -HHost:httpbin.example.com "http://$GATEWAY_URL/headers"
{
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.example.com",
    "My-Added-Header": "added-value",  # 添加了一个Header头
    "User-Agent": "curl/7.29.0",
    "X-Envoy-Attempt-Count": "1",
    "X-Envoy-Internal": "true",
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/httpbin;Hash=11fc66a1c6c9fc44e65ec67f7f8b16d06fbaa73d9729e141e0bd91134dc59db3;Subject=\"\";URI=spiffe://cluster.local/ns/istio-ingress/sa/gateway-istio"
  }
}

在上面的示例中,在配置网关之前,我们并没有去安装 Ingress 网关的 Deployment,因为在默认配置中会根据 Gateway 配置自动分发网关 Deployment 和 Service。但是对于高级别的场景可能还是需要去手动部署。

自动部署

默认情况下,每个 Gateway 将自动提供相同名称的 Service 和 Deployment。如果 Gateway 发生变化(例如添加了一个新端口),这些配置将会自动更新。这些资源可以通过以下几种方式进行定义:

  • 将 Gateway 上的注解和标签复制到 Service 和 Deployment。这就允许配置从上述字段中读取到的内容,如配置内部负载均衡器等。Istio 提供了一个额外的注解来配置生成的资源:
  • networking.istio.io/service-type:控制 Service.spec.type 字段。例如设置 ClusterIP 为不对外暴露服务,将会默认为 LoadBalancer。
  • 通过配置addresses字段可以显式设置Service.spec.loadBalancerIP字段:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: gateway
spec:
addresses:
    - value: 192.0.2.0 # 仅能指定一个地址
    type: IPAddress

手动部署

如果您不希望使用自动部署,可以进行手动配置 Deployment 和 Service。完成此选项后,您将需要手动将 Gateway 链接到 Service,并保持它们的端口配置同步。

要将 Gateway 链接到 Service,需要将 addresses 字段配置为指向单个 Hostname。

apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
  name: gateway
spec:
  addresses:
    - value: ingress.istio-gateways.svc.cluster.local
      type: Hostname

当然我们这里只是一个最简单的示例,我们将在后面的课程中继续介绍 Gateway API 的更多功能。

配置请求路由

接下来我们来了解下如果通过 Gateway API 将请求动态路由到微服务的多个版本。

同样我们这里以 Bookinfo 示例为例(首先要部署 Bookinfo 应用),我们首先将所有流量路由到微服务的 v1 (版本 1),然后将应用规则根据 HTTP 请求 header 的值路由流量。

首先专门为 Bookinfo 应用创建一个 Gateway 资源对象,如下所示:

# bookinfo-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: bookinfo-gateway
spec:
  gatewayClassName: istio
  listeners:
    - name: http
      port: 80
      protocol: HTTP
      allowedRoutes:
        namespaces:
          from: Same

上面的 Gateway 资源对象与之前的示例类似,只是这里我们将 allowedRoutes 设置为 Same,表示允许同一命名空间中的所有路由资源对象都可以连接到这个网关。

然后为 Productpage 应用创建一个 HTTPRoute 资源对象,如下所示:

# productpage-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: bookinfo
spec:
  parentRefs:
    - name: bookinfo-gateway # 引用上面定义的 Gateway 对象
  rules:
    - matches:
        - path:
            type: Exact
            value: /productpage
        - path:
            type: PathPrefix
            value: /static
        - path:
            type: Exact
            value: /login
        - path:
            type: Exact
            value: /logout
        - path:
            type: PathPrefix
            value: /api/v1/products
      backendRefs:
        - name: productpage # 引用的后端服务
          port: 9080

和以前 VirtualService 的类似,我们为 Productpage 应用配置了几个路由规则,这样我们就可以在页面上正常访问应用了。直接应用上面的两个资源清单文件即可:

kubectl apply -f bookinfo-gateway.yaml
kubectl apply -f productpage-route.yaml

同样当我们创建了 Gateway 对象后,会自动在 default 命名空间中部署一个 gateway-istio 的 Deployment 和 对应的 Service:

$ kubectl get pods -l istio.io/gateway-name=bookinfo-gateway
NAME                                      READY   STATUS    RESTARTS   AGE
bookinfo-gateway-istio-548556df95-kggs2   1/1     Running   0          112s
$ kubectl get svc bookinfo-gateway-istio
NAME                     TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                        AGE
bookinfo-gateway-istio   LoadBalancer   10.111.86.147   <pending>     15021:30357/TCP,80:30749/TCP   2m19s

正常我们就可以通过上面的 30749 这个 NodePort 端口来访问 Productpage 应用了:

bookinfo

路由到版本 1

同样的页面上的评论区域会出现 3 种不同的状态,因为我们背后有 3 个不同的版本的评论服务,我们可以创建一个如下所示的 HTTPRoute 资源对象,首先将流量路由到 v1 版本:

# route-reviews-v1.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: reviews
spec:
  parentRefs: # 这里我们引用的是 reviews 这个 Service 对象
    - group: ""
      kind: Service
      name: reviews
      port: 9080
  rules:
    - backendRefs:
        - name: reviews-v1
          port: 9080

注意上面的资源对象中我们是通过 parentRefs 字段引用的是 reviews 这个 Service 对象,而不是 Gateway 对象了,因为我们流量并不是从 Gateway 直接过来的,而是通过 Productpage 访问 reviews 服务,也就是 Service 对象。然后背后的我们会将流量全部路由到 v1 版本的 reviews 服务 reviews-v1。

现在我们再应用上面的这个资源对象:

kubectl apply -f route-reviews-v1.yaml

不过这里需要注意不同于 Istio API 使用 DestinationRule 子集来定义服务的版本, Kubernetes Gateway API 将为此使用后端 Service 服务来进行定义。所以我们还需要运行以下命令为三个版本的 reviews 服务创建后端服务定义:

kubectl apply -f samples/bookinfo/platform/kube/bookinfo-versions.yaml

然后我们可以通过再次刷新 Bookinfo 应用程序,现在我们无论刷新多少次,页面的评论部分都不会显示评级星标,这是因为我们将 Istio 配置为将评论服务的所有流量路由到版本 reviews:v1,而此版本的服务不访问星级评分服务。

reviews v1

基于用户身份路由

接下来我们来更改路由配置,将来自特定用户的所有流量路由到特定服务版本,比如将来自名为 Jason 的用户的所有流量被路由到服务 reviews:v2(包含星级评分功能的版本)。

创建一个如下所示的 HTTPRoute 资源对象:

# route-reviews-jason-v2.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: reviews
spec:
  parentRefs:
    - group: ""
      kind: Service
      name: reviews
      port: 9080
  rules:
    - matches:
        - headers: # 匹配请求头 end-user: jason
            - name: end-user
              value: jason
      backendRefs:
        - name: reviews-v2
          port: 9080
    - backendRefs:
        - name: reviews-v1
          port: 9080

在上面的 HTTPRoute 资源对象中,我们将访问 reviews 的流量分为两个规则,第一个规则匹配请求头 end-user: jason,然后将流量路由到 reviews-v2 服务,第二个则是如果不匹配将流量路由到 reviews-v1 服务。

然后我们再次应用这个资源对象:

kubectl apply -f route-reviews-jason-v2.yaml

然后我们在 Bookinfo 页面上面,可以以用户 jason 身份进行登录,登录后无论如何刷新浏览器,我们将始终在页面上看到黑色的星级评分,也就是 reviews 的 v2 版本。

jason 用于

我们也可以切换成其他用户,或者不登录,刷新浏览器,那么就不会显示星级评分了。

基于权重的路由

接下来我们再来测试下基于权重的路由,常常我们有将流量从微服务的一个版本逐步迁移到另一个版本的需求,同样使用 Gateway API 来实现也非常简单。

下面我们将会把 50% 的流量发送到 reviews:v1,另外,50% 的流量发送到 reviews:v3。接着,再把 100% 的流量发送到 reviews:v3 来完成迁移。

首先重新运行下面的命令将所有流量路由到 review 服务的 v1 版本。

kubectl apply -f route-reviews-v1.yaml

接下来创建如下所示的资源对象,把 50% 的流量从 reviews:v1 转移到 reviews:v3:

# route-reviews-50-v3.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: reviews
spec:
  parentRefs:
    - group: ""
      kind: Service
      name: reviews
      port: 9080
  rules:
    - backendRefs:
        - name: reviews-v1
          port: 9080
          weight: 50
        - name: reviews-v3
          port: 9080
          weight: 50

上面的对象中在 backendRefs 中我们使用了一个 weight 字段,用于设置权重,这里我们将 reviews-v1 和 reviews-v3 的权重都设置为 50,也就是说将流量平均分配到这两个版本的服务上。

然后我们应用这个资源对象即可:

kubectl apply -f route-reviews-50-v3.yaml

等待几秒钟,等待新的规则传播到代理中生效,然后我们再次刷新浏览器中的 productpage 页面,大约有 50% 的几率会看到页面中带红色星级的评价内容。 这是因为 reviews 的 v3 版本可以访问带星级评价,但 v1 版本不能。

如果你认为 reviews:v3 微服务已经稳定,那么接下来我们就可以将 100% 的流量路由 reviews:v3:

# route-reviews-v3.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: reviews
spec:
  parentRefs:
    - group: ""
      kind: Service
      name: reviews
      port: 9080
  rules:
    - backendRefs:
        - name: reviews-v3
          port: 9080

然后我们再次应用这个资源对象:

kubectl apply -f route-reviews-v3.yaml

现在,当我们访问 Bookinfo 应用时,将始终看到带有红色星级评分的书评。

使用 TLS 暴露服务

接下来我们来看下如何通过 TLS 来暴露服务,这里我们以 httpbin 示例进行说明。

首先要部署 httpbin 示例:

kubectl apply -f samples/httpbin/httpbin.yaml

然后接下来生成客户端和服务器证书和密钥,这里我们使用 openssl 工具来生成。

首先创建用于服务签名的根证书和私钥:

$ mkdir example_certs1
$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example_certs1/example.com.key -out example_certs1/example.com.crt

为 httpbin.example.com 创建证书和私钥:

$ openssl req -out example_certs1/httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization"
$ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 0 -in example_certs1/httpbin.example.com.csr -out example_certs1/httpbin.example.com.crt

然后接下来需要为入口网关创建 Secret:

kubectl create -n istio-system secret tls httpbin-credential \
  --key=example_certs1/httpbin.example.com.key \
  --cert=example_certs1/httpbin.example.com.crt

这里我们创建一个独立的 Kubernetes Gateway:

# httpbin-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: mygateway
  namespace: istio-system
spec:
  gatewayClassName: istio
  listeners:
    - name: https
      hostname: "httpbin.example.com"
      port: 443
      protocol: HTTPS
      tls:
        mode: Terminate
        certificateRefs:
          - name: httpbin-credential
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              kubernetes.io/metadata.name: default

在上面的对象中我们配置了 HTTPS 协议,并在 tls 中配置使用 Terminate 模式,也就是说我们将在网关上终止 TLS 连接,然后在 certificateRefs 中引用了之前创建的 Secret 对象,用于配置网关的凭据。最后通过 allowedRoutes 字段配置了允许的路由,这里我们配置的是将流量路由到 default 命名空间中的所有路由资源对象。

接下来,通过定义相应的 HTTPRoute 配置网关的入口流量路由:

# httpbin-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: httpbin
spec:
  parentRefs:
    - name: mygateway
      namespace: istio-system
  hostnames: ["httpbin.example.com"]
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /status
        - path:
            type: PathPrefix
            value: /delay
      backendRefs:
        - name: httpbin
          port: 8000

这里最重要的就是在 parentRefs 字段中引用了之前创建的 Gateway 对象。

然后我们直接应用上面的两个资源对象即可:

kubectl apply -f httpbin-gateway.yaml
kubectl apply -f httpbin-route.yaml

同样应用后会自动在 istio-system 命名空间中部署一个 gateway-istio 的 Deployment 和对应的 Service:

$ kubectl get pods -n istio-system
NAME                                       READY   STATUS    RESTARTS         AGE
mygateway-istio-64676bfc88-q8nft           1/1     Running   0                32s
$ kubectl get svc -n istio-system
NAME                      TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                                                                      AGE
mygateway-istio           LoadBalancer   10.111.175.36    <pending>     15021:31206/TCP,443:32597/TCP                                                74s

然后我们可以通过 32597 这个 NodePort 端口向 httpbin 服务发送 HTTPS 请求:

$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:32597:192.168.0.100" --cacert example_certs1/example.com.crt "https://httpbin.example.com:32597/status/418"

* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: example_certs1/example.com.crt
  CApath: none
* SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate:
*       subject: O=httpbin organization,CN=httpbin.example.com
*       start date: Dec 22 07:13:47 2023 GMT
*       expire date: Dec 21 07:13:47 2024 GMT
*       common name: httpbin.example.com
*       issuer: CN=example.com,O=example Inc.
# ......

    -=[ teapot ]=-

       _...._
     .'  _ _ `.
    | ."` ^ `". _,
    \_;`"---"`|//
      |       ;/
      \_     _/
        `"""`
* Connection #0 to host httpbin.example.com left intact

正常我们就可以看到输出一个茶壶,这样我们完成了通过 TLS 来暴露服务。

TCP 路由

除了 HTTP 路由外,Gateway API 还支持 TCP 和 UDP 路由,配置 TCP 的路由规则,需要使用单独的资源对象 TCPRoute,如下所示:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: tcp-echo-gateway
spec:
  gatewayClassName: istio
  listeners:
    - name: tcp-31400
      protocol: TCP
      port: 31400
      allowedRoutes: # 只允许 TCPRoute 资源对象连接到这个网关
        kinds:
          - kind: TCPRoute
---
apiVersion: v1
kind: Service
metadata:
  name: tcp-echo-v1
spec:
  ports:
    - port: 9000
      name: tcp
  selector:
    app: tcp-echo
    version: v1
---
apiVersion: v1
kind: Service
metadata:
  name: tcp-echo-v2
spec:
  ports:
    - port: 9000
      name: tcp
  selector:
    app: tcp-echo
    version: v2
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TCPRoute # TCPRoute 资源对象
metadata:
  name: tcp-echo
spec:
  parentRefs: # 引用定义的 Gateway 对象
    - name: tcp-echo-gateway
      sectionName: tcp-31400
  rules:
    - backendRefs:
        - name: tcp-echo-v1
          port: 9000
          weight: 80
        - name: tcp-echo-v2
          port: 9000
          weight: 20

其他使用

其他的流量管理比如故障注入、熔断这些,Gateway API 尚不支持。

但是支持请求超时,比如对 reviews 服务的调用增加一个半秒的请求超时,可以使用下面的资源对象:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: reviews
spec:
  parentRefs:
    - group: ""
      kind: Service
      name: reviews
      port: 9080
  rules:
    - backendRefs:
        - name: reviews-v2
          port: 9080
      timeouts:
        request: 500ms

上面的对象中我们添加了一个 timeouts 字段,用于设置请求超时时间。

同样还支持流量镜像,如下的资源对象:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: httpbin
spec:
  parentRefs:
    - group: ""
      kind: Service
      name: httpbin
      port: 8000
  rules:
    - filters:
        - type: RequestMirror
          requestMirror:
            backendRef:
              name: httpbin-v2
              port: 80
      backendRefs:
        - name: httpbin-v1
          port: 80

在上面的资源对象中我们添加了一个 RequestMirror 过滤器,该过滤器用于将流量镜像到另外的服务上去。

责任编辑:姜华 来源: k8s技术圈
相关推荐

2021-07-27 06:51:53

Istio 微服务Service Mes

2023-11-09 07:23:57

Istio路由分析

2021-07-28 06:26:33

Istio 流量管理微服务

2022-05-10 07:46:08

Envoy网络通讯

2023-11-07 17:32:31

Istiok8s

2022-01-06 07:46:01

Traefik 开源Gateway API

2024-07-05 11:13:05

2021-11-01 08:16:26

模型Istio服务

2023-11-08 00:23:08

网关API

2024-09-23 08:03:59

2023-06-05 08:00:00

mTLSIstio安全

2022-11-24 08:35:28

KitexProxyless

2023-04-18 15:18:10

2024-05-09 08:24:01

2019-08-22 09:55:17

RedisAPI数据

2024-01-30 07:58:41

KubernetesGAMMA网关

2023-11-07 07:08:57

2023-11-07 07:46:02

GatewayKubernetes

2023-06-26 18:13:56

开源API

2021-08-03 14:12:57

API攻击流量安全
点赞
收藏

51CTO技术栈公众号