在 Kubernetes 集群边缘对外提供网络服务的时候,通常需要借助 Ingress 对象,这个对象提供了暴露 Service 所必须的核心要素,例如基于主机名的路由、对 URL 路径的适配以及 TLS 配置等。但是在实际开放服务的时候,往往会有更多的具体需求,这时 Ingress 对象所提供的核心功能就有些力不从心了,各种 Ingress 控制器往往会使用 metadata.annotations
中的特定注解,来完成对 Ingress 特定行为的控制,完成各自的个性化功能,例如认证、路径变更、黑白名单等,这就让 Ingress 对象变成了一个奇怪的东西:结构化的核心结构,和非结构化的标注结合起来形成各种 Ingress 方言,并且后期还出现了 Traefik Middleware 这样的 CRD 配置,这给 Ingress 功能的集中管理造成了一个较大的困扰;另外 Ingress 中可以随意定制主机名、路径以及后端服务,也给共享集群的用户造成了一定的安全隐患。包括 Cotour、Traefik 在内的 Ingress 控制器后期都提供了各自的基于 CRD 的功能表达,客观上也让 Ingress 世界更为分裂。
例如要移除路径前缀,Nginx Ingress 控制器需要使用 nginx.ingress.kubernetes.io/rewrite-target
注解,而 Traefik 1.7 中则需要使用 traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip
注解。
SIG-Network 基于实际现状和需求,提出了全新的 Gateway API 来作为 Ingress 的继任者,总体来说,相对于 Ingress,Gateway API 有几个显著特点:
职责分离,运维、开发等不同的角色都能够在适合的边界内完成工作;
扩展核心能力,并使用更结构化的方式进行表达;
易于扩展:Gateway API 为各种不同实现的控制器提供了一致的扩展方法。
目前该 API 还处于 Alpha 阶段,也仅有少量控制器提供了早期支持。下面做一些陈述和试验,来看看 Gateway API 有什么不一样。
概念层次
Ingress 中包含了 IngressClass/Ingress 两层概念,而 Gateway API 包含了三层概念:GatewayClass、Gateway 和 Route,其中的 Route 实际是包含了 HTTPRoute、TCPRoute、TLSRoute 和 UDPRoute 在内的一组对象。
GatewayClass
它是一个集群范围内的资源,由云基础设施中的 Gateway API 控制器提供,其职责和原有的 Ingress Class 类似。
Gateway
Gateway 对象是命名空间范围对象,可以视作是 GatewayClass 的一个实例,它通常是由具体机群的运维人员进行维护的,在 Gateway 对象中可以指定该对象负责的主机名称范围,用标签选择器选择对应的 Service,甚至还可以指定该 Gateway 生效的命名空间。这样就给具体应用的对外开放划定了一个范围,防止应用随意占用主机名,并完善命名空间的隔离能力。
Route
前文讲到,Route 对象除了像原有的 Ingress 对象一样提供 HTTP 服务的开放能力之外,还提供了 TCP、TLS 和 UDP 的对应资源,从而缓解了 Nginx、HAProxy Ingress 控制器使用 Configmap 配置 TCP/UDP 的窘境。HTTPRoute 除了提供基础的 Ingress 对象能力之外,还提供了一些“越界”的功能,例如对流量进行复制、分流;更重要的是其中还提供了 Filter 能力,这是一个扩展点,除了自带的核心处理能力之外,底层设施还可以在这里接入自己的 CRD,对流量进行处理,从而为流量处理能力的扩展提供了一个统一入口。UDPRoute 和 TCPRoute 也提供了对流量的判别能力,但是这部分仅提供了扩展点,而没有像 HTTP 一样的成熟能力。
举个栗子
目前 GKE 提供了 Gateway API 的公共预览版可以用于测试,仅限于以下区域的 1.20 以上版本的集群:
us-west1
us-east1
us-central1
europe-west4
europe-west3
europe-west2
europe-west1
asia-southeast1
不同区域的集群缺省开关可能不一致,注意需要在控制台的网络页面启用 HTTP 负载均衡功能,或者在命令行中的 --addons
参数值里加入 HttpLoadBalancing
。
使用如下命令部署网关资源:
$ kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v0.3.0" \
| kubectl apply -f -
customresourcedefinition.apiextensions.k8s.io/backendpolicies.networking.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/gatewayclasses.networking.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/gateways.networking.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/httproutes.networking.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/tcproutes.networking.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/tlsroutes.networking.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/udproutes.networking.x-k8s.io created
部署完成之后,网关控制器会被触发,创建两个 GatewayClass
:
$ kubectl get gatewayclasses
NAME CONTROLLER AGE
gke-l7-gxlb networking.gke.io/gateway 1s
gke-l7-rilb networking.gke.io/gateway 1s
不难发现,我们使用的是 HTTP 负载均衡,新建的 Gateway Class 也都包含 l7
字样,其实官方文档也明确说明:
注意:GKE Gateway Controller 仅支持 GatewayClass、网关和 HTTPRoute。不支持 TCPRoute、UDPRoute 和 TLSRoute。
这里初始化了两个 GatewayClass,gxlb
用于外部,rilb
用于内部,所以我们要在外网测试,就要用 gxlb
创建网关。
一个简单的工作负载:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: flaskapp-v1
name: flaskapp-v1
spec:
replicas: 1
selector:
matchLabels:
app: flaskapp-v1
template:
metadata:
labels:
app: flaskapp-v1
spec:
containers:
- image: dustise/flaskapp:v0.2.6
name: flaskapp
env:
- name: "VERSION"
value: "v1"
---
apiVersion: v1
kind: Service
metadata:
labels:
app: flaskapp-v1
name: flaskapp-v1
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: flaskapp-v1
type: ClusterIP
创建 Gateway:
kind: Gateway
apiVersion: networking.x-k8s.io/v1alpha1
metadata:
name: gateway-gen
spec:
gatewayClassName: gke-l7-gxlb
listeners:
- protocol: HTTP
port: 80
hostname: "*.microservice.xyz"
routes:
kind: HTTPRoute
selector:
matchLabels:
app: flaskapp-v1
namespaces:
from: "All"
这个 Gateway 对象中仅定义了一个 Listener 对象,其中规定可以使用的域名为 *.microservice.xyz
的 HTTPRoute
,选择器要求使用这个对象的 Route 必须使用 app=flaskapp-v1
的标签,可以在所有命名空间中进行引用。创建之后查看一下状态:
$ kubectl describe gateway gateway-gen
...
Spec:
Gateway Class Name: gke-l7-gxlb
Listeners:
Hostname: *.microservice.xyz
Port: 80
Protocol: HTTP
Routes:
Group: networking.x-k8s.io
Kind: HTTPRoute
Namespaces:
From: All
Selector:
Match Labels:
App: flaskapp-v1
Status:
Addresses:
Type: IPAddress
Value: 37.132.121.12
...
看到 Gateway 中已经得到了一个 IP 地址。接下来建立一个对应的 HTTPRoute 对象:
kind: HTTPRoute
apiVersion: networking.x-k8s.io/v1alpha1
metadata:
name: flaskapp-v1
labels:
app: flaskapp-v1
spec:
hostnames:
- "v1.microservice.xyz"
- "v1.microservice.rocks"
rules:
- forwardTo:
- serviceName: flaskapp-v1
port: 80
这个里面我们用了一个多余的域名:v1.microservice.rocks
,看看运行结果:
$ http http://v1.microservice.xyz/env/VERSION
HTTP/1.1 200 OK
...
Server: nginx/1.15.3
Via: 1.1 google
v1
$ http http://v1.microservice.rocks/env/VERSION
HTTP/1.1 404 Not Found
...
Via: 1.1 google
default backend - 404
这里看到,“超纲”的域名会返回 404,被缺省后端截获。如果更换一个命名空间来创建 HTTPRoute 来引用 Gateway,会发现虽然在 Gateway 中定义了 namespaces.from: "All"
,但是仍旧会返回 404
,describe httproute
一下会发现,spec.gateways.allow
缺省被设置为 SameNamespace
,因此显式定义 spec.gateways.allow=All
,就能正常访问了。
分流
HTTPRoute 的 spec.rules
是一个数组,实际上这是一个分流支持,例如我们如此定义:
rules:
- forwardTo:
- serviceName: flaskapp-v1
port: 80
- forwardTo:
- serviceName: flaskapp-v2
port: 80
然后循环测试,会发现 v1
和 v2
在一定时间内会交替出现。forwardTo
还有一个 weight
属性,这个数字决定了流量在不同转发目标之间的分配比例。
GKE 的分流好像比较弱,一百个请求测试,有时分配也并不明显。
条件分支
多个 Rule 之间还可以使用条件进行分流,例如:
rules:
- forwardTo:
- serviceName: flaskapp-v1
port: 80
- forwardTo:
- serviceName: flaskapp-v2
port: 80
matches:
- headers:
type: Exact
values:
version: v2
测试一下有无特定 Header 的结果:
$ http http://v1.microservice.xyz/env/VERSION version:v2
...
v2
$ http http://v1.microservice.xyz/env/VERSION
...
v1
其他
在直接的 forwardTo
之外,Gateway API 还可以通过 Filter 的方式支持扩展能力,这个能够在转发之前进行流量处理的功能分为三种层级:
Core:所有实现者都必须实现该能力,例如
RequestHeaderModifier
;Extended:建议实现这个层级的能力,例如
RequestMirror
;Custom:实现者可以在这个层级实现各种扩展能力,如果多个厂商都实现了该功能,则可能升级到 Extended 或者 Core。
GKE 的公共 Gateway 并不支持流量复制,现阶段也不提供 TCP/UDP 的支持,可能需要靠其它控制器来实现。