引言
要是你读过我之前的任何文章,就会知道我选用了一家名为Binary Lane的澳大利亚云服务提供商。这家提供商提供的服务稳定、性价比高,不过服务覆盖范围相对有限。
服务范围有限意味着,所有与Kubernetes相关的工作都得你自己动手完成,但这也为你创造了学习契机,让你能够自主掌控解决方案的运行模式。而且,这还避免了你被某一家云服务供应商所束缚,同时也具备成本优势。
在这篇文章里,我将深入探讨如何在Kubernetes集群中添加Kong API网关,从而实现对服务的访问。由于涉及的内容较多,本文篇幅会比较长。因此,我把Kubernetes集群中关于API网关的理论部分单独写在了另一篇文章中。要是你对API网关的作用还不太清楚,我建议你先去阅读那篇文章。
虽然Kong是一款备受信赖的企业级API网关,但它的设置过程可能颇具挑战性。在本文结尾处,我会分享一些调试问题的技巧。要是你在参考本文进行设计调整,一定要确保名称和端口配置正确无误。
网络设计
Kubernetes网络是一个复杂的话题,我无法在这里全面讲解,但我们确实需要在高层次上考虑网络设计。
倘若你一直在持续阅读我的其他文章,那么你理应已在Binary Lane(或者其他云服务提供商)的服务器上成功安装了一个Kubernetes集群。在虚拟私有云(VPC)的私有子网中,你安装了三个节点,该子网无法直接通过互联网访问,除非借助一个专用的VPN(在上文中未展示)。还会有一个网关服务器,它同时具备互联网接口和VPC接口,为集群构建了从互联网进入集群的入口(以及从集群通向互联网的出口,这部分在前面的文章中已经完成设置)。
至此,我们搭建好了基础的网络拓扑结构。现在,我们期望能够有效管理服务向外界开放的API,这将借助Kong Gateway API来达成。
从互联网连接(Ingress)
对于像AWS、Google Cloud或Azure这类功能完备的服务提供商而言,你可以利用他们的服务来设置Kubernetes,从而让其自动为你创建互联网连接。这一过程是通过一种类型为LoadBalancer
的Kubernetes服务来实现的。
虽然Binary Lane提供了负载均衡器服务,然而这些服务无法通过Kubernetes进行管理,所以你必须自行创建并配置负载均衡器,或者打造属于自己的入口点。为了避免受限于特定云提供商的功能,我更倾向于在我的gw服务器上运行一个NGINX反向代理,以此创建自己的入口点。
在这种架构中,运行于gw服务器上的NGINX承担着两个关键功能:
- 请求路由:将所有合法有效的请求转发至Kubernetes集群,交由Kong进行后续处理。
- 负载均衡:在Kubernetes节点之间对请求进行负载均衡分配。
由于Kong将以NodePort服务的形式暴露,所以它能够从集群中的任意节点进行访问。这就使得NGINX能够在节点之间对请求进行负载均衡操作。
值得一提的是,服务本身会在可用的pod之间进行随机负载均衡,所以NGINX所进行的负载均衡更多是为了在节点出现故障或过载时,仍能确保服务的持续运行。你可以在此处了解更多关于服务负载均衡的详细信息。
在这个解决方案中,你可以将NGINX看作是一个手动配置的、替代常规外部负载均衡器的工具。
Kubernetes服务
Kubernetes服务的核心作用,是为访问那些可能由一个或多个pod共同提供的服务搭建桥梁。它的强大之处在于,即便某个pod被终止后重新调度,哪怕新的运行节点发生了变化,服务依然能够作为一个稳定的单一联系点,精准地将请求按需路由到对应的pod上,而无论请求源自集群中的哪一个节点。
借助Service,你将获得一个独一无二且稳定不变的IP地址,所有请求都能被引导至此。同时,Service还提供了一种在众多可用pod之间进行负载均衡的有效机制,确保资源的合理分配与高效利用。此外,Kubernetes会在集群的DNS中自动添加对Service的引用,这一贴心设计使得服务可以直接通过名称进行访问。在实际应用中,存在多种不同的服务名称注册变体,具体如下:
<service name>.<namespace>.svc.local
<service name>.<namespace>.svc
<service name>.<namespace>
<service name>
API网关
虽然NGINX网关和Kubernetes服务在为你的服务搭建外部接口时能够发挥一定作用,但它们的功能存在局限性,并且还需手动进行设置。
API网关则是解决这一问题的关键集群组件。它作为你解决方案中的重要一环,部署在你的服务前端,用于提供我在此所描述的一系列额外功能。
作为集群的有机组成部分,API网关能够依据集群内部资源的动态变化,自动完成配置调整,极大地提升了系统的灵活性和适应性。
Kong API 网关
市面上存在多种不同的API网关技术,而在本文中,我选用了Kong。Kong既提供免费的开源版本,也有付费的企业安装版,我们将采用其开源版本。若想深入了解Kong,你可以在此处找到详细的官方文档。
Kong一直以来都在积极与Kubernetes社区展开合作,共同致力于定义集群中网关的全新标准。这一合作成果催生了一种全新的Kubernetes资源类型——Gateway API,需要注意的是,千万不要将它与实际的网关本身相互混淆。
以下是Kong在较高层面的工作原理:
任何来自集群外部客户端的流量,都会首先进入Kong。Kong会依据自身配置中预设的规则,对请求进行一系列处理,随后将处理后的请求传递至集群内部或外部的对应服务。
Kong获取配置信息的来源有两种:一是Kubernetes资源清单(无数据库安装模式),二是数据库。目前,Kong推荐新安装采用无数据库安装模式,我们也将遵循这一建议。
Kong具备成熟的插件扩展能力,这使得第三方能够以插件的形式开发扩展Kong的功能。这些插件嵌入在流量流中,能够辅助处理诸如速率限制、身份验证等重要问题。
最后,为了便于管理,Kong提供了一个管理UI,通过这个界面,你可以便捷地管理插件、配置参数等内容。管理UI通过Admin API与Kong进行交互通信。
查阅Kong的详细文档后不难发现,Kong的功能十分丰富,远超我们在本文所能涵盖的范畴,因此,我仅会为你介绍其中的基础知识。
无数据库安装
深入理解无数据库安装的运行机制至关重要。
在这种配置模式下,Kong会安装两个关键组件:
- Kong Ingress Controller (KIC)
- Kong Gateway
Kong Gateway负责通过其代理功能,对所有用户流量进行路由处理。而Kong Ingress Controller (KIC)则从Kubernetes资源定义(如HTTPRoute)中获取配置信息,并将这些信息转化为代理能够识别和执行的规则,然后实时上传至代理。如此一来,Kubernetes配置发生的任何更改,都能自动同步应用到代理上。
KIC借助内部的Kubernetes API来获取Kubernetes集群的相关信息,这个API主要用于集群的管理工作。当你使用kubectl命令行工具时,实际上就是在与Kubernetes API进行交互。借助这个API,像Kong和kubectl这类应用程序,不仅能够获取集群的状态信息,还能对集群进行相应的更改操作。正是通过这个API,KIC得以确保Kong Gateway与集群资源文件保持同步,而无需依赖数据库进行数据备份。
注意:在无数据库安装模式下,Kong会将内部状态存储在内存中。这就导致管理用户界面只能读取状态信息,而无法对其进行更新。此外,通过Admin API在网关上执行的部分操作(比如设置调试级别)会返回错误,提示没有数据库支持。这意味着配置工作必须通过Helm图表值文件或Kubernetes资源定义文件来完成。
部署测试服务
在部署Kong之前,确保拥有一些可通过Kong进行访问的服务至关重要。毕竟,若没有可供处理的API,API网关也就失去了实际意义!
创建服务时,最简单的方式是将NGINX部署为配置了静态内容的Web服务器。要是你此前从未在Kubernetes上进行过此类操作,可以查阅我其他文章中介绍的具体方法。
我建议你创建两个服务:
- Hello world 1:设置2个副本
- Hello world 2:设置1个副本
这些服务应当能够通过ClusterIP服务,被集群中的pod访问到。在我介绍设置此类服务的文章里,我创建了NodePort服务,这样就能从浏览器对服务进行检查。如果采用这种方式,务必记住要使用内部集群IP和端口,而非外部的IP和端口。关于服务类型的更多详细信息,你可以阅读我另一篇文章。
检查这两个服务是否已成功启动并正常运行,并且确认它们能够提供包含“Hello World”的HTML页面。
在我此处描述的示例中,这两个服务的访问地址如下:
http://<节点IP地址>:30082
— 该地址会回复“Hello World 1 !!”http://<节点IP地址>:30082
— 此地址会回复“Hello World 2 !!”
需要注意的是,这两个服务均不接受任何路径访问。若你添加路径(例如:http://<节点IP地址>:30082/world1
),将会收到404错误。这一点务必牢记,因为若未注意到这一情况,很可能在后续操作中引发问题。
现在,我们的服务已顺利启动并运行,接下来就让我们尝试通过Kong来访问它们。
创建Kubernetes Gateway资源
在无数据库模式下,我们借助创建Kubernetes Ingress
或HTTPRoute
资源,来对Kong API网关进行配置。一旦这些资源应用到集群当中,Kong就会依据它们在代理组件里定义路由规则,从而将传入的流量精准定向到你的服务。
虽说Ingress
资源能够发挥作用,但其功能较为有限。而由Kong与Kubernetes社区携手开发的全新Gateway
资源,可助力你更高效、更高级地管理API。
我们将在使用Kong时搭配Gateway
资源。要实现这一点,首先得把支持GatewayClass
和Gateway
资源的新自定义资源定义(CRD)应用到我们的集群中。你可以在运行集群的kubectl所在位置执行下面这条命令来完成操作,就我而言,是在我的k8s-master服务器上执行。
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml`
这里还有一些实验性功能,在本文中我们不会用到,但为了方便你参考,我还是把它们列出来。
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/experimental-install.yaml
现在,我们可以为Kubernetes定义两个关键资源:GatewayClass和Gateway。这两个资源的作用我在本文前面部分已经解释过,这里就不再赘述,以保持内容简洁。
GatewayClass
我们要定义一个GatewayClass,以此将Kong技术引入到我们的集群。创建如下文件:
kong-gw-class.yml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: kong-class
annotations:
konghq.com/gatewayclass-unmanaged: 'true'
spec:
controllerName: konghq.com/kic-gateway-controller
对于这个文件,有这么几点值得关注:
- 它没有设置命名空间,因为它属于集群级别的资源。
- 注释部分用于定义针对该解决方案的特定选项,就Kong来说,这些选项都是以konghq开头。
konghq.com/gatewayclass-unmanaged
注释设置为字符串“true”,这是因为Kong是通过手动方式进行设置的,而不是借助操作器自动设置(当然,自动设置也是一种可行的方式,详细内容可查看此处 )。- Ingress Controller采用的是Kong Ingress Controller(
konghq.com/kic-gateway-controller
),并在controllerName
字段里完成配置。
现在,通过以下命令创建这个类:
kubectl apply -f kong-gw-class.yml
完成上述操作后,我们就可以创建一个使用该类的Gateway了。需要注意的是,你能够创建多个引用同一GatewayClass的Gateway实例 。
Gateway
对于像我们正在创建的这种手动安装的Kong Gateway,你需要创建如下文件:
kong-gw-gateway.yml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: kong-gateway
namespace: kong
spec:
gatewayClassName: kong-class
listeners:
- name: world-selector
hostname: worlds.com
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: All
同样,对于这个文件,有以下要点需要留意:
- 它拥有一个名称(
kong-gateway
),这个名称方便后续在各种操作中进行引用。 - GatewayClass字段引用了我们之前创建的GatewayClass的名称,以此建立两者之间的关联,确保配置的连贯性和准确性。
- 此Gateway仅定义了一个监听器,这个监听器作为整个API Gateway的入口点,所有进入网关的流量都将首先经过它的处理。
- 为监听器赋予的名称是唯一的,并且符合URL规范,这样可以确保在网络通信中能够准确识别和调用。
- 监听器绑定到端口80,这是HTTP协议常用的端口,用于接收相应的网络请求。
- 主机名作为匹配字段,虽然在某些情况下不是必需的,但在这里可以根据实际需求进行灵活配置,以满足不同的路由规则。
- 通过
allowedRoutes
中的命名空间设置,你能够控制哪些服务可以连接到这个监听器。默认情况下,通常是与Gateway相同的命名空间。但在我们的场景中,由于希望连接到其他命名空间的服务,所以将其设置为“All”,表示允许所有命名空间的服务连接。 - Gateway的规范明确要求网关通过HTTP协议监听单个端口,这里设置为80。
- Gateway是特定于某个命名空间的,所以在安装API Gateway之前,我们需要先创建对应的命名空间。执行以下命令即可创建:
kubectl create namespace kong
命名空间创建完成后,就可以创建资源了,执行命令:
kubectl apply -f kong-gw-gateway.yml
到目前为止,我们已经成功定义了GatewayClass和Gateway资源,接下来就可以安装应用程序本身了,它将作为这两个资源的具体实现,从而构建完整的API网关体系。
安装Kong
接下来,我们将借助Helm chart来安装Kong。如果你还未安装Helm,可以参考这篇文章了解安装方法。
Kong CRDs
在安装Kong之前,我们要先安装Kong的自定义资源定义(CRDs)。你可以在运行kubectl
的环境中执行以下命令来完成安装,对我来说,是在k8s - master服务器上操作。
注意,这里要使用-k
选项而非常规的-f
选项。-k
选项会触发kustomize功能,从指定文件夹中安装一组清单文件。
kubectl apply -k https://github.com/Kong/kubernetes-ingress-controller/config/crd
执行过程中,你可能会看到一些关于缺少依赖的警告信息,不必为此担心,因为这些依赖会自动安装。
Kong应用程序
将Kong安装到Kubernetes集群时,会同时安装两个组件:
- Kong Ingress Controller (KIC):负责将Kubernetes资源定义转换为Kong网关的配置。
- Kong Gateway:依据Kong Ingress Controller插入的配置,充当服务的路由器。
首先,把Kong仓库添加到本地Helm:
helm repo add kong https://charts.konghq.com
helm repo update
若你搜索Helm图表:
helm search repo kong
会看到两个条目:
NAME CHART VERSION APP VERSION DESCRIPTION
kong/kong 2.33.3 3.5 The Cloud - Native Ingress and API - management
kong/ingress 0.10.2 3.4 Deploy Kong Ingress Controller and Kong Gateway
由于我们希望采用无数据库(DB - less)配置,所以选择使用kong/ingress
。在安装之前,需要覆盖一些默认值。创建以下文件:
kong-values.yml
#controller:
# ingressController:
# env:
# LOG_LEVEL: trace
# dump_config: true
gateway:
admin:
http:
enabled: true
proxy:
type: NodePort
http:
enabled: true
nodePort: 32001
tls:
enabled: false
# ingressController:
# env:
# LOG_LEVEL: trace
因为我们通过父Helm chart来安装KIC和Kong Gateway,这两个应用程序的配置分别位于controller
和gateway
标签下。我保留controller
部分,是为了给你提个醒。
你还会看到一些被注释掉的行,如果你想通过Pod日志调试问题,这些配置会很有用。
我们对代理配置进行了覆盖,原因是Binary Lane不提供可由Kubernetes配置的负载均衡器。因此,我们让Kubernetes设置一个NodePort服务,而非LoadBalancer服务。我们将网关暴露在端口32001上,该端口在集群的所有节点上都可用。
之前我们已经创建了kong
命名空间,现在可以安装Kong了:
helm install kong kong/ingress -f kong-values.yml -n kong
你可以检查安装是否按预期完成,这可能需要一两分钟:
kubectl get all -n kong
输出结果应该类似如下内容:
NAME READY STATUS RESTARTS AGE
pod/kong-controller-68cddcbcb7-z46lh 1/1 Running 0 45s
pod/kong-gateway-687c5b78db-5qvgd 1/1 Running 0 45s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kong-controller-validation-webhook ClusterIP 10.110.172.40 <none> 443/TCP 46s
service/kong-gateway-admin ClusterIP None <none> 8444/TCP 46s
service/kong-gateway-manager NodePort 10.100.254.169 <none> 8002:30698/TCP,8445:30393/TCP 46s
service/kong-gateway-proxy NodePort 10.96.24.196 <none> 80:32001/TCP 46s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/kong-controller 1/1 1 1 45s
deployment.apps/kong-gateway 1/1 1 1 45s
NAME DESIRED CURRENT READY AGE
replicaset.apps/kong-controller-68cddcbcb7 1 1 1 45s
replicaset.apps/kong-gateway-687c5b78db 1 1 1 45s
你会注意到有一个通过NodePort暴露的管理UI服务,但由于它需要访问admin API,而我们采用的是无数据库(DB - less)安装,所以该管理UI目前只能用于查看配置。
你可以在集群节点上使用curl
命令测试部署情况:
curl localhost:32001
你应该会得到如下响应:
{
"message":"no Route matched with those values",
"request_id":"7fc9db053e3029105581890e81effe12"
}
request_id
是此次事务的唯一标识符,再次运行curl
命令时,你会看到不同的值。这是Kong添加的,方便你在系统中跟踪请求,是不是很实用?
现在,你可以开始配置新的API Gateway,将请求路由到之前创建的测试服务了。
添加路由
借助HTTPRoute
资源,能够为你的Gateway
资源添加路由。针对不同层级的路由需求,还存在其他资源类型。当前,我们要创建一个这样的资源,将主机worlds.com/world1
连接至hello-world-1-svc
,将worlds.com/world2
连接至hello-world-2-svc
。
我先为你详细描述一个HTTPRoute资源,之后你便能依葫芦画瓢创建另一个。请创建资源文件:
hello-world-1-route.yml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-1
annotations:
konghq.com/strip-path: 'true'
spec:
parentRefs:
- name: kong-gateway
namespace: kong
hostnames:
- worlds.com
rules:
- matches:
- path:
type: PathPrefix
value: /world1
backendRefs:
- name: hello-world-1-svc
port: 80
kind: Service
在这个文件里,我们添加了一个专属于Kong的注释konghq.com/strip-path: 'true'
,它的作用是在将请求传递给下游服务之前,把传入且匹配的路径从请求中剥离出去。文件里的其他关键内容包括:
- Gateway的定义引用:在
ParentRefs
字段中,通过名称和命名空间来引用所使用的Gateway,明确路由与网关的关联。 - 可选主机名引用:用于匹配Gateway中对应的监听器,确保请求能够准确地被相应的监听器接收和处理。
- 请求匹配规则:用于判断哪些传入请求符合此路由的条件,只有满足规则的请求才会被该路由处理。
- 后端服务定义:
backendRefs
定义了匹配请求将被路由到的服务。需注意,这里的名称是服务的内部DNS名称,端口则是服务未映射的clusterIP端口。
在此路由配置中,匹配的是/world1
前缀,并且在将请求传递给服务之前,这个前缀会被剥离。
现在,我们通过以下命令来创建此路由:
kubectl apply -f hello-world-1-route.yml
由于我们创建的网关采用了NodePort服务,现在就可以对服务进行测试了。NodePort服务在集群中的所有节点上都可访问。我通常选用k8s-master节点进行测试,但实际上任何节点都能用于测试。现在你可以执行如下测试命令:
curl -H "Host: worlds.com" <k8s-master IP地址>:32001/world2
可以看到,我将主机名设置为worlds.com
作为Host头,这样做是为了让路由能够正常生效。执行命令后,你应该能看到测试服务返回的响应。
现在,你可以添加第二个HTTPRoute资源,用于管理对第二个服务的请求。
目前,我们的服务已经能够在集群节点上被访问,接下来可以进行最后一步——配置gw服务器。
配置入口点
如果我们是在AWS、Azure或Google Cloud等云平台上部署,就可以选择将Gateway服务保留为LoadBalancer类型,它们会自动为我们创建一个入口点。但由于我们使用的是Binary Lane,所以必须手动创建入口点。
要是你一直关注我的文章,就会知道我有一台gw服务器,它作为从Internet到集群的入口点,本质上是一个简单的NGINX部署。
我们要将其配置为使用轮询负载均衡器,把所有请求路由到集群中的每个节点。
登录到gw
服务器,以root用户身份更新以下文件(请务必用你自己的值替换< >
中的字段):
/etc/nginx/sites-available/worlds.conf
upstream k8s_cluster {
server <k8s-master>:32001;
server <k8s-node1>:32001;
server <k8s-node2>:32001;
}
server {
listen 80;
listen [::]:80;
server_name worlds.com;
location / {
proxy_pass http://k8s_cluster;
include proxy_params;
}
}
通常,代理参数会在一个单独的文件中进行设置:
/etc/nginx/proxy_params
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
这些配置会传递Host
头以及其他关键细节,这是确保路由能够正常生效所必需的。
现在,启用站点,测试配置,然后重启NGINX:
ln -s /etc/nginx/sites-available/worlds.conf /etc/nginx/sites-enabled/
nginx -t
systemctl restart nginx
现在你可以通过以下命令进行测试(用你自己的值替换< >
中的字段):
curl -H "Host: worlds.com" <gw服务器公共IP地址>/world1
你应该能够收到服务器返回的响应。
恭喜你,你已经成功安装Kong,并完成了将其配置为连接到你的服务的所有操作。
注意:当前这个设置还不能直接用于生产环境。我们还没有实现任何安全的TLS连接,无论是到gw服务器的连接,还是集群内部的连接。这是后续需要开展的另一个项目。另外,我也没有介绍如何创建你自己的域名,并将其链接到gw服务器的公共地址。如果你有这方面的需求,记得在所有相关地方更改对
worlds.com
的引用。
调试Kong
要是Kong出了问题,调试起来可能颇具挑战。下面是我在安装Kong过程中总结出的一些实用调试技巧:
- 巧用
kubectl describe
:通过这个命令查看GatewayClass、Gateway以及controller/gateway pod的详细信息,并且要仔细研读结果。我曾经在解决问题时卡了很久,最后正是这些信息为我指明了解决方向。 - 查看日志:执行以下命令查看controller和gateway的日志:
kubectl logs <pod name> -n kong
- 调整日志级别:借助
kong-values.yml
文件来提高日志级别(把之前提到的相关行的注释取消掉即可)。 - 访问管理UI:首先得用
kubectl get svc -n kong
获取NodePort地址。获取地址后,使用HTTP端口,接着通过下面的命令对Admin API进行端口转发(注意添加--address
选项,以便在外部绑定服务):kubectl port-forward <gateway pod name> 8001:8001 --address <k8s-master IP address>
- 借助REST API工具:完成8001端口的转发后,就可以通过像Postman这样的REST API工具来访问Admin UI了。
- 访问调试端口:还能通过以下命令访问一个调试端口:
kubectl port-forward -n kong <controller pod name> 10256:10256 --address <k8s-master IP address>
总结
这篇文章篇幅较长,但这是安装像Kong这样的API网关所必需的,因为要把各个步骤整合起来。
在本文中,我们完成了以下内容:
- 回顾网络拓扑:梳理了我们的网络拓扑结构。
- 了解服务作用:明白了服务是如何帮助我们访问外部服务的。
- 探究Kong与Kubernetes协作:看到了Kong是怎样与Kubernetes协同工作的。
- 创建测试服务:搭建了一些测试服务,以便后续使用。
- 配置关键资源:安装并配置了GatewayClass和Gateway资源。
- 安装与配置Kong:完成了Kong的安装和配置工作。
- 搭建外部负载均衡器:配置了属于我们自己的外部负载均衡器。
- 探讨调试方法:思考了调试API网关安装问题的办法。
最终,我们成功实现了通过Kong API网关从互联网访问我们的测试服务。
要是你觉得这篇文章有意思,麻烦给我点个赞。这能让我清楚大家觉得哪些内容有价值,进而明确未来文章的创作方向。要是你有任何建议,欢迎在评论区畅所欲言。
发布时间: 2025-02-26
作者: 技术书栈编辑