我是 LEE,老李,一个在 IT 行业摸爬滚打 17 年的技术老兵。
事件背景
这段时间忙完公司内部的 Serverless 工程模型验收,算是有一点时间腾出来了。实际也不知道要分享什么,实际在设计、实现和验收 Serverless 工程模型过程中有很多可以说。但是最后想了想,还是分享工程中用到的 Kubernetes-sigs 中的好用“工具” -- Gateway API。为什么要说 Gateway API 呢?不妨先说说他给我们带来什么好处:内部发布系统从此不需要在针对 Traefik 和 Istio IngressGateway 两种 Gateway 独立开发接口,同时每次底层 Gateway Pod 版本更新或者业务在 Traefik 和 Istio IngressGateway 之间迁移的时候,还要单独考虑接口的兼容性。 这个赞,一下屏蔽了很多问题,也减少了其他部门开发的难度和心智负担。
这下一不小心就让 Serverless 的 Gateway 统一纳入了到了 Gateway API 管理中,让底层平台和产品以“惊人”的速度上线,而且内部平台小伙伴在实现 Gateway API 调用的时候,Serverless 的 Gateway 接入过程中不用修改任何一行代码就打通了上下游功能关系,瞬间得到了很多开发系统小组的支持和认可。
此时各位看官是不是也有点心动呢?想试试 Kubernetes-sigs 的 Gateway API 呢?
前置知识
Gateway API 介绍
大家一定都知道 Kubernetes 中的 Ingress API 是对集群中服务外部访问进行管理的 API 对象,同时也可以提供负载均衡,SSL 卸载以及虚拟路由等功能。Ingress API 也伴随着 Kubernetes 稳定服役了 5 年之久,但是慢慢随着集群规模的扩大,业务管理的复杂化,Ingress API 渐渐无法满足日益多样化的需求,发现 Ingress API 提供的功能太少且很难被扩展,各个厂商都提供了不同的扩展方式,例如加入不同的 annotation, 这导致应用很难在不同的厂商之间无缝迁移, 并且提供的权限管理模型也过于单一。
于是 Kubernetes 社区便孕育出了新一代的流量管理标准:Gateway API。相较于 Ingress API, Gateway API 的主要改进在于:
- 提供了比 Ingress API 更丰富的,具有可扩展性的 API 集合,例如基于 Header 的流量匹配,流量加权以及其他在 Ingress API 中需要自定义注释实现的功能。
- Gateway API 的设计是角色导向的,它允许不同的团队共享网络基础设施,并且通过角色策略约束不同角色的行为。
- Gateway API 也添加了对东西向(Mesh)流量管理的支持,为此各大 Service Mesh 厂商还成立了 GAMMA(Gateway API Mesh Management and Administration)[3]工作小组来探索和讨论在 Gateway API 中处理东西向流量的规范。
Gateway API 核心组件
在理解 Kubernetes Gateway API 是如何工作之前,需要先对其中几个基本资源类型有个大致的了解:
- GatewayClass: 类似于 Ingress 中的 Ingress Class 对象,定义了一组共享通用配置和行为的 Gateway 集合,一个 GatewayClass 被一个 Controller 控制。
- Gateway: 描述了流量被分配到集群中服务的方式,可以直接由管理员创建,也可以由控制 GatewayClass 的 Controller 创建。
- Route: 描述了通过网关的流量如何映射到服务,目前 Gateway API 中提供了五种不同协议的 Route,分别是 HTTPRoute,TLSRoute,TCPRoute,UDPRoute 和 GRPCRoute。(本文重点在 HTTPRoute)
核心组件之间逻辑关系:
核心组件之间流量关系:
GatewayClass
GatewayClass 用大白话说:是一个 Gateway 的提供商,比如:Istio、Traefik、APISIX、Nginx 等等都算是,他们要根据 Kubernetes-sigs 的 Gateway API 的标准和要求,实现一个 Controller,能够让 Gateway 能够依赖 GatewayClass 创建对应的真实网关。
GatewayClass 可以说成模板,生成器都行。
官方文档:gateway-api.sigs.k8s.io/api-types/g…
下面我们用 Istio 1.16.2 举例。看看 Istio 在安装后,默认会生成的一个 GatewayClass 的内容。
apiVersion: gateway.networking.k8s.io/v1beta1
kind: GatewayClass
metadata:
name: istio
spec:
controllerName: istio.io/gateway-controller
description: The default Istio GatewayClass
TIPS: 一般 GatewayClass 不需要人工手动创建。
Gateway
Gateway 用大白话说:是 GatewayClass 的一个衍生物,是通过配置创建出来的一个资源。在创建一个 Gateway 资源的时候,会创建 Gateway 的 Pod,它由 GatewayClass 提供 Gateway 镜像产生(举例:如果是 Istio,产生的 Gateway 就是 IngressGateway 使用的镜像)。
Gateway 可以说成 GatewayClass 的具体实现,一个具体存在的 Pod,负责流量接入以及往后转发。
官方文档:gateway-api.sigs.k8s.io/api-types/g…
举一个 Gateway 配置的例子,看看 Gateway 是如何创建出来的。
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: serverless-ingress
namespace: gateway-api-istio
spec:
gatewayClassName: istio ## 关联指定的 GatewayClass,名字必须与 GatewayClass 中定义的名称相同
listeners:
- allowedRoutes:
namespaces:
from: All ## 允许 Gateway 往所有的 Namespace 的 Pod 转发流量,这个一般是常用模式。
name: http
port: 80 ## 网关监听端口
protocol: HTTP ## 网关使用的协议
产生的 Gateway Pod
$ kubectl get pod -n gateway-api-istio
NAME READY STATUS RESTARTS AGE
serverless-ingress-59cb9b9cdc-vrthr 1/1 Running 0 6d17h
TIPS: Gateway 基本上是使用 Kubernetes-sigs 的 Gateway API 的第一步。
HttpRoute
这里只说 HttpRoute,其他的 Route 模型,小伙伴可以参考官方文档中的内容。目前 HttpRoute 是相对成熟的,而且我们业务主要是 Http 协议,所以对 HttpRoute 做了相对的深入研究。
HttpRoute 用大白话说:真实路由。解决流量在满足特定的转发规则的情况下,如何转发到后端。
HttpRoute Kubernetes-sigs 的 Gateway API 中最核心的部分,所有流量控制和调度都抽象到这里 (没有 Istio 中 VirtualService 和 DestinationRule 之间的复杂的逻辑关系)。
官方文档:gateway-api.sigs.k8s.io/api-types/h…
我们参考下面的例子。这是一个具备灰度流量转发,以及修改 Host 头部的 yaml 文件内容。 主要是体现 HttpRoute 丰富的控制能力,当然这里要多花时间才能理清里面的逻辑关系。
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: helloworld-go
namespace: default
spec:
hostnames:
- "*.kt.com"
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: serverless-ingress ## HttpRoute 注册到哪个 Gateway,换句话说给哪个网关添加路由
namespace: gateway-api-istio
rules:
- backendRefs:
- group: ""
kind: Service
name: helloworld-go
port: 80
weight: 1
filters:
- requestHeaderModifier:
set:
- name: host
value: v2-helloworld-go.default
type: RequestHeaderModifier
matches:
- headers:
- name: env
type: Exact
value: canary
path:
type: PathPrefix
value: /
- backendRefs:
- group: ""
kind: Service
name: helloworld-go
port: 80
weight: 1
filters:
- requestHeaderModifier:
set:
- name: host
value: v1-helloworld-go.default
type: RequestHeaderModifier
matches:
- path:
type: PathPrefix
value: /
如何部署
如果业务模型是以 Http 协议为主的,基本没有什么 TCP 协议的业务,同时环境里面有多种的 Gateway 模型(Istio、Traefik、APISIX、Nginx 等),或者经常更新 Gateway 版本或者升级的用户,可以考虑使用下 Kubernetes-sigs 的 Gateway API。 当然 Gateway API 部署起来也非常的方便,只需要一个 yaml 文件就可以解决。
使用安装包 standard-install.yaml 直接 apply 就行。
$ kubectl apply -f standard-install.yaml
查看下创建的 Pod
$ kubectl get pod -n gateway-system
NAME READY STATUS RESTARTS AGE
gateway-api-admission-d95fp 0/1 Completed 0 18d
gateway-api-admission-patch-h9lvt 0/1 Completed 0 18d
gateway-api-admission-server-65cf9bbbc8-f5jtr 1/1 Running 0 18d
gateway-api-admission-d95fp 和 gateway-api-admission-patch-h9lvt 这两个 Pod 没有 Running 是正常的,是因为就是在 gateway-api-admission-server-65cf9bbbc8-f5jtr 启动之前,做了一些任务。实际可以直接删除,因为已经执行过了。我这边资源非常充足,所以不删也行。只要看到 gateway-api-admission-server-65cf9bbbc8-f5jtr 是 Running 状态就行。
有的小伙伴看到这里,Kubernetes-sigs 的 Gateway API 部署就这么简单? 真的就这么简单。本身就是一个协议的抽象层,为什么一定会部署非常复杂呢? 嘿嘿!!!!
虽然我是这么说容易,但是实际部署的过程中还是碰到了一些让人挠头的问题,比较容易让小伙伴的踩坑。 这边部署 Gateway API 环境都是 Istio,所以创建的 Gateway 实际也是一种 Istio IngressGateway,当然下面的问题也是基于 Istio。
问题 1:创建 Gateway Pod 启动失败,image: auto
在部署了 Gateway 的时候,发现 Pod 始终不能到 Running 状态,通过 describe pod 信息看到 Pod 是因为 image:auto 无法拉取到镜像,导致启动失败。
原因是创建 Gateway 资源所在的 Namespace 没有被 Istio 自动注入,导致无法触发到 WebHook ,从而 Gateway 的 image 信息无法正确读取,导致启动失败。
解决办法:只要在 Namespace 上打上一个标签 istio-injection=enabled, 重启 Gateway Pod 就可以了。
问题 2:Gateway Pod 只有一个,没有对应的 HPA
在部署了 Gateway 的时候,Pod 已正常启动,到了 Running 状态。 但是 Gateway Pod 只有 1 个,明显不能真正满足实际生产需要,多数情况至少需要多个 Pod 一起负载压力。
查看 HPA 相关配置,发现没有对网关做任何 HPA 配置
$ kubectl get hpa -n gateway-api-istio
No resources found in gateway-api-istio namespace.
针对创建出来的 Gateway,手动单独配置 HPA,发现无法正常创建 HPA。
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: serverless-ingress
namespace: gateway-api-istio
spec:
maxReplicas: 3
metrics:
- resource:
name: cpu
target:
averageUtilization: 80
type: Utilization
type: Resource
minReplicas: 1
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: serverless-ingress
解决办法:是因为创建 Gateway 的 Deployment 中没有对应的 request/limit 相关资源,需要自己手动补下就行。
如何使用
当你部署完毕了,所有的相关资源都启动正常,就可以部署一个 demo 应用来测试效果了。
我这边使用的 Istio 作为 Gateway API 的实现,那么我也用 Istio 传统 demo 测试应用(httpbin)来做演示。
安装 httpbin
$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/1.16.0/samples/httpbin/httpbin.yaml
刷入 GatewayApi 配置
apiVersion: v1
kind: Namespace
metadata:
labels:
istio-injection: enabled ## 这里一定要启动注入,要不然就碰到前面的问题1
name: istio-ingress
spec:
finalizers:
- kubernetes
---
apiVersion: gateway.networking.k8s.io/v1beta1
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
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: http
namespace: default
spec:
parentRefs:
- name: gateway
namespace: istio-ingress
hostnames: ["httpbin.example.com"]
rules:
- matches:
- path:
type: PathPrefix
value: /get
backendRefs:
- name: httpbin
port: 8000
测试访问:
$ curl -s -I -HHost:httpbin.example.com "http://$INGRESS_HOST/get"
HTTP/1.1 200 OK
server: istio-envoy
...
如何开发
目前 Kubernetes-sigs 的 Gateway API 开发包只支持 Go,用其他语言的小伙伴可能要等等看看社区有没有相关的实现。
用 Go 的小伙伴要开发 Gateway API 相关功能也非常的简单。
- 拉取 go package
go get -u sigs.k8s.io/gateway-api
- 然后 import 下面的路径
sigs.k8s.io/gateway-api/apis/v1beta1
这里写一个 demo 程序供大家参考:
package main
import (
"context"
"flag"
"fmt"
"path/filepath"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1"
)
func main() {
var kubeconfig *string
if home := homedir.HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
fmt.Println(err)
return
}
dynamicSet, err := dynamic.NewForConfig(config)
if err != nil {
fmt.Println(err)
return
}
gwGVR := schema.GroupVersionResource{Version: "v1beta1", Group: "gateway.networking.k8s.io", Resource: "gateways"}
unstructObj, err := dynamicSet.Resource(gwGVR).Namespace("").List(context.TODO(), metav1.ListOptions{Limit: 100})
if err != nil {
fmt.Println(err)
return
}
var gw gatewayapi.Gateway
for _, item := range unstructObj.Items {
err := runtime.DefaultUnstructuredConverter.FromUnstructured(item.UnstructuredContent(), &gw)
if err != nil {
fmt.Printf("failed to Unstructured Gateway object, error: %s\n", err.Error())
continue
}
fmt.Println("[Gateway] >>>>>>", gw.Name, gw.Namespace)
}
httpRouteGVR := schema.GroupVersionResource{Version: "v1beta1", Group: "gateway.networking.k8s.io", Resource: "httproutes"}
unstructObj, err = dynamicSet.Resource(httpRouteGVR).Namespace("").List(context.TODO(), metav1.ListOptions{Limit: 100})
if err != nil {
fmt.Println(err)
return
}
var hr gatewayapi.HTTPRoute
for _, item := range unstructObj.Items {
err := runtime.DefaultUnstructuredConverter.FromUnstructured(item.UnstructuredContent(), &hr)
if err != nil {
fmt.Printf("failed to Unstructured HTTPRoute object, error: %s\n", err.Error())
continue
}
fmt.Println("[HttpRoute] >>>>>>", hr.Name, hr.Namespace)
}
time.Sleep(10 * time.Second)
}
Console 输出:
$ go run 1.go
[Gateway] >>>>>> serverless-ingress gateway-api-istio
[HttpRoute] >>>>>> helloworld-go default
写在最后
Kubernetes-sigs 的 Gateway API 已经完成我们内部的工程模型测试,同时整个技术方案也通过技术委员会的认可,已经作为 2023 年容器专项落实技术之一。在 2023 整个一年中,我们几乎是全部的跟路由相关的平台都会迁移到这套接口上。虽然 Kubernetes-sigs 的 Gateway API 才到了 0.6.1 的版本,却已经达到了我们内部评定的要求,已经基本达到了可用状态,同时也具备了比较完整的生产力。