Kong工作原理 - 代理参考 (Proxy Reference)

在本文档中,我们详细介绍了Kong Gateway的代理功能,包括其路由能力和内部工作原理。

Kong Gateway提供了一些接口,可以通过以下配置属性进行调整:

  • proxy_listen,定义了Kong Gateway将接受来自客户端的公共HTTP(gRPC,WebSocket等)流量并代理到upstream 服务的地址/端口列表(默认为8000)。

  • admin_listen,同样定义了地址和端口列表,但这些应该限制为仅由管理员访问,因为它们暴露了Kong的配置能力:Admin API(默认为8001)。

    重要提示:如果您需要在生产环境中将admin_listen端口暴露给Internet,

    请使用身份验证进行安全保护。

  • stream_listen类似于proxy_listen,但用于层4(TCP、TLS)通用代理。默认情况下,此功能已关闭。

术语(Terminology)

  • 客户端(Client):指从下游发起请求到Kong Gateway的代理端口的客户端。
  • 上游服务(Upstream service):指您自己的API/服务,位于Kong Gateway后面,将客户端的请求/连接转发到此服务上。
  • 服务(Service):服务实体,顾名思义,是您自己的每个上游服务的抽象。服务的示例可能是数据转换微服务、计费API等。
  • 路由(Route):这指的是Kong Gateway的路由实体。路由是进入Kong Gateway的入口点,定义请求匹配规则,并将其路由到指定的服务。
  • 插件(Plugin):这是指Kong Gateway的"插件",它们是在代理生命周期中运行的业务逻辑组件。插件可以通过Admin API进行配置,可以全局配置(适用于所有传入流量),也可以针对特定的路由和服务进行配置。

概览

从高层次的角度来看,Kong Gateway在其配置的代理端口(默认为8000和8443)上监听HTTP流量,并在显式配置的stream_listen端口上监听L4流量。Kong Gateway将根据您配置的路由对任何传入的HTTP请求或L4连接进行评估,并尝试找到匹配的路由。如果给定的请求符合特定路由的规则,Kong Gateway将处理请求的代理过程。

由于每个路由可能与一个服务相关联,Kong Gateway将在您配置的路由及其关联的服务上运行已配置的插件,然后将请求代理到上游。您可以通过Kong Gateway的Admin API管理路由。路由具有用于路由和匹配传入HTTP请求的特殊属性。不同子系统(HTTP/HTTPS、gRPC/gRPCS和TCP/TLS)的路由属性会有所不同。

子系统和路由属性:

HTTP:

  • 方法(Methods)

  • 主机(Hosts)

  • 头部(Headers)

  • 路径(Paths)(如果是HTTPS,还包括SNIs)

TCP:

  • 源地址(Sources)

  • 目标地址(Destinations)(如果是TLS,还包括SNIs)

gRPC:

  • 主机(Hosts)

  • 头部(Headers)

  • 路径(Paths)(如果是gRPCS,还包括SNIs)

如果您尝试配置一个具有不支持的路由属性的路由(例如,使用源地址或目标地址字段的HTTP路由),系统将报告一个错误消息:

Lua 复制代码
HTTP/1.1 400 Bad Request
Content-Type: application/json
Server: kong/<x.x.x>

{
   "code": 2,
   "fields": {
       "sources": "cannot set 'sources' when 'protocols' is 'http' or 'https'"
   },
   "message": "schema violation (sources: cannot set 'sources' when 'protocols' is 'http' or 'https')",
   "name": "schema violation"
}

如果Kong Gateway收到一个无法与任何配置的路由匹配的请求(或者没有配置路由),它将做出以下响应:

Lua 复制代码
HTTP/1.1 404 Not Found
Content-Type: application/json
Server: kong/<x.x.x>

{
   "message": "no route and no Service found with those values"
}

如何配置一个服务

将服务添加到Kong Gateway中是通过向Admin API发送HTTP请求来完成的。

Lua 复制代码
curl -i -X POST http://localhost:8001/services/ \
 -d 'name=foo-service' \
 -d 'url=http://foo-service.com'

响应:

Lua 复制代码
HTTP/1.1 201 Created
...

{
   "connect_timeout": 60000,
   "created_at": 1515537771,
   "host": "foo-service.com",
   "id": "d54da06c-d69f-4910-8896-915c63c270cd",
   "name": "foo-service",
   "path": "/",
   "port": 80,
   "protocol": "http",
   "read_timeout": 60000,
   "retries": 5,
   "updated_at": 1515537771,
   "write_timeout": 60000
}

这个请求指示Kong Gateway注册一个名为"foo-service"的服务,该服务指向http://foo-service.com(您的上游服务)。

注意:url参数是一个简写参数,一次性填充协议(protocol)、主机(host)、端口(port)和路径(path)属性。

现在,为了通过Kong Gateway发送流量到该服务,我们需要指定一个路由,它作为Kong Gateway的入口点:

Lua 复制代码
curl -i -X POST http://localhost:8001/routes/ \
 -d 'hosts[]=example.com' \
 -d 'paths[]=/foo' \
 -d 'service.id=d54da06c-d69f-4910-8896-915c63c270cd'

响应:

Lua 复制代码
HTTP/1.1 201 Created
...

{
   "created_at": 1515539858,
   "hosts": [
       "example.com"
   ],
   "id": "ee794195-6783-4056-a5cc-a7e0fde88c81",
   "methods": null,
   "paths": [
       "/foo"
   ],
   "preserve_host": false,
   "priority": 0,
   "protocols": [
       "http",
       "https"
   ],
   "service": {
       "id": "d54da06c-d69f-4910-8896-915c63c270cd"
   },
   "strip_path": true,
   "updated_at": 1515539858
}

我们现在已经配置了一个路由,用于匹配与给定主机和路径相符的传入请求,并将它们转发到我们配置的foo-service,从而代理此流量到http://foo-service.com

Kong Gateway是一个透明代理,默认情况下将请求原封不动地转发到您的上游(upstream )服务,除了根据HTTP规范需要的Connection、Date和其他各种标头之外。

路由和匹配能力

现在让我们讨论一下Kong Gateway如何根据配置的路由属性来匹配请求。

Kong Gateway支持原生的HTTP/HTTPS、TCL/TLS和GRPC/GRPCS协议的代理;正如之前提到的,每个协议都接受不同的路由属性集合:

  • HTTP:方法(methods)、主机(hosts)、头部(headers)、路径(paths)(如果是HTTPS,还包括SNIs)
  • TCP:源地址(sources)、目标地址(destinations)(如果是TLS,还包括SNIs)
  • gRPC:主机(hosts)、头部(headers)、路径(paths)(如果是GRPCS,还包括SNIs)

请注意,所有这些字段都是可选的,但至少必须指定其中一个。

对于请求匹配一个路由:

  • 请求必须包含所有配置的字段

  • 请求中的字段值必须与至少一个配置的值匹配(虽然字段配置接受一个或多个值,但只需要请求中的一个值与配置值相匹配就被认为是匹配的)

让我们通过几个示例来说明。考虑一个配置如下的路由:

Lua 复制代码
{
   "hosts": ["example.com", "foo-service.com"],
   "paths": ["/foo", "/bar"],
   "methods": ["GET"]
}

一些可能匹配此路由的请求示例如下:

Lua 复制代码
GET /foo HTTP/1.1
Host: example.com
Lua 复制代码
GET /bar HTTP/1.1
Host: foo-service.com
Lua 复制代码
GET /foo/hello/world HTTP/1.1
Host: example.com

这三个请求都满足路由定义中的所有条件。

然而,以下请求将不符合配置的条件:

Lua 复制代码
GET / HTTP/1.1
Host: example.com
Lua 复制代码
POST /foo HTTP/1.1
Host: example.com
Lua 复制代码
GET /foo HTTP/1.1
Host: foo.com

这三个请求只满足了两个配置条件。第一个请求的路径与任何配置的路径都不匹配,第二个请求的HTTP方法也是如此,第三个请求的Host头部同样如此。

现在我们已经了解了路由属性如何一起工作,让我们分别探讨每个属性。

请求头

Kong Gateway支持根据任意HTTP头部进行路由。其中的一个特例是通过Host头部进行路由。

基于请求的Host头部进行路由是通过Kong Gateway代理流量的最直接的方式,特别是因为这也是HTTP Host头部的预期用法。Kong Gateway通过路由实体的hosts字段,使此操作变得容易。

hosts字段接受多个值,当通过Admin API指定时,这些值必须使用逗号分隔,并以JSON负载表示:

Lua 复制代码
curl -i -X POST http://localhost:8001/routes/ \
 -H 'Content-Type: application/json' \
 -d '{"hosts":["example.com", "foo-service.com"]}'

响应:

Lua 复制代码
HTTP/1.1 201 Created
...

由于Admin API也支持form-urlencoded内容类型,您可以使用[]表示法指定一个数组:

Lua 复制代码
curl -i -X POST http://localhost:8001/routes/ \
 -d 'hosts[]=example.com' \
 -d 'hosts[]=foo-service.com'

响应:

Lua 复制代码
HTTP/1.1 201 Created
...

为了满足此路由的hosts条件,任何来自客户端的传入请求现在必须将其Host头部设置为以下之一:

Lua 复制代码
Host: example.com

或者:

Lua 复制代码
Host: foo-service.com

类似地,任何其他头部都可以用于路由:

Lua 复制代码
curl -i -X POST http://localhost:8001/routes/ \
 -d 'headers.region=north'

响应:

Lua 复制代码
HTTP/1.1 201 Created

包含设置为North的Region头部的传入请求将被路由到该路由。

Lua 复制代码
Region: North

使用通配符主机名

为了提供灵活性,Kong Gateway允许您在hosts字段中使用通配符来指定主机名。通配符主机名允许任何匹配的Host头部满足条件,从而匹配给定的路由。

通配符主机名必须在域的最左边或最右边的标签中只包含一个星号。例如:

  • *.example.com将允许a.example.com和x.y.example.com等Host值匹配。
  • example.*将允许example.com和example.org等Host值匹配。

一个完整的示例如下所示:

Lua 复制代码
{
   "hosts": ["*.example.com", "service.com"]
}

这将允许以下请求与此路由匹配:

Lua 复制代码
GET / HTTP/1.1
Host: an.example.com
Lua 复制代码
GET / HTTP/1.1
Host: service.com

preserve_host属性

在代理时,Kong Gateway的默认行为是将upstream请求的Host头部设置为服务的主机名。preserve_host字段接受一个布尔标志,指示Kong Gateway不要这样做。

例如,当preserve_host属性没有更改且路由配置如下时:

Lua 复制代码
{
   "hosts": ["service.com"],
   "service": {
       "id": "..."
   }
}

客户端发送给Kong Gateway的可能请求示例如下:

Lua 复制代码
GET / HTTP/1.1
Host: service.com

Kong Gateway会从服务的host属性中提取Host头部的值,并发送以下的上游请求:

Lua 复制代码
GET / HTTP/1.1
Host: <my-service-host.com>

然而,通过显式配置preserve_host=true的路由:

Lua 复制代码
{
   "hosts": ["service.com"],
   "preserve_host": true,
   "service": {
       "id": "..."
   }
}

假设来自客户端的相同请求:

Lua 复制代码
GET / HTTP/1.1
Host: service.com

Kong Gateway将保留客户端请求中的Host,并发送以下的上游请求:

Lua 复制代码
GET / HTTP/1.1
Host: service.com
额外的请求头部

可以通过除了Host以外的其他头部来路由请求。

要做到这一点,请使用路由中的headers属性:

Lua 复制代码
{
   "headers": { "version": ["v1", "v2"] },
   "service": {
       "id": "..."
   }
}

考虑到一个具有类似以下请求头部的请求:

Lua 复制代码
GET / HTTP/1.1
version: v1

这个请求将被路由到该服务。同样的情况也适用于这个请求:

Lua 复制代码
GET / HTTP/1.1
version: v2

但是这个请求没有被路由到该服务:

Lua 复制代码
GET / HTTP/1.1
version: v3

注意:headers的键使用逻辑与运算,它们的值使用逻辑或运算。

请求路径

路由匹配的另一种方式是通过请求路径。为了满足这个路由条件,客户端请求的标准化路径必须以paths属性的一个值开头。

例如,使用如下配置的路由:

Lua 复制代码
{
   "paths": ["/service", "/hello/world"]
}

以下请求将匹配此路由:

Lua 复制代码
GET /service HTTP/1.1
Host: example.com
Lua 复制代码
GET /service/resource?param=value HTTP/1.1
Host: example.com
Lua 复制代码
GET /hello/world/resource HTTP/1.1
Host: anything.com

对于这些请求中的每一个,Kong Gateway会检测到它们的标准化URL路径以routes的paths值之一作为前缀。默认情况下,Kong Gateway将无需更改URL路径将请求代理到上游。

在使用路径前缀进行代理时,最长的路径首先进行评估。这样可以确保您定义的两个路径为/service和/service/resource的路由不会相互遮挡。

在路径中使用正则表达式

对于要被视为正则表达式的路径,必须以~作为前缀:

Lua 复制代码
paths: ["~/foo/bar$"]

任何没有以~作为前缀的路径都将被视为纯文本:

Lua 复制代码
"paths": ["/users/\d+/profile", "/following"]

评估顺序

路由器使用路由配置中的regex_priority字段评估路由。更高的regex_priority值表示更高的优先级。

Lua 复制代码
[
   {
       "paths": ["~/status/\d+"],
       "regex_priority": 0
   },
   {
       "paths": ["~/version/\d+/status/\d+"],
       "regex_priority": 6
   },
   {
       "paths": /version,
   },
   {
       "paths": ["~/version/any/"],
   }
]

在这种情况下,Kong Gateway按照以下顺序评估传入的请求对定义的URI进行匹配:

  1. /version/\d+/status/\d+
  2. /status/\d+
  3. /version/any/
  4. /version

具有大量正则表达式的路由器可能会占用本应用于其他规则的流量。构建和执行正则表达式要耗费更多资源,并且不容易优化。您可以使用路由器表达式语言来避免创建复杂的正则表达式。

如果您遇到意外的行为,请使用Kong的调试头部来帮助追踪问题的来源:

  • 在kong.conf中,将allow_debug_header设置为on。
  • 在您的请求头部中发送Kong-Debug: 1,以便在响应头部中指示匹配的路由ID,用于故障排除目的。

与往常一样,一个请求仍然必须匹配路由的hosts和methods属性,Kong Gateway会遍历您的路由直到找到匹配最多规则的路由。

捕获组

捕获组也被支持,匹配的组将从路径中提取出来,并可以供插件使用。如果我们考虑以下的正则表达式:

Lua 复制代码
/version/(?<version>\d+)/users/(?<user>\S+)

以及以下请求路径:

Lua 复制代码
/version/1/users/john

Kong Gateway认为请求路径匹配,并且如果整个路由也匹配(考虑其他路由属性),提取的捕获组将在ngx.ctx变量中对插件可用:

Lua 复制代码
local router_matches = ngx.ctx.router_matches

-- router_matches.uri_captures is:
-- { "1", "john", version = "1", user = "john" }

转义特殊字符

接下来值得注意的是,根据RFC 3986,正则表达式中的字符通常被视为保留字符,因此应进行百分比编码。当通过Admin API配置具有正则表达式路径的路由时,如果有必要,请确保对有效负载进行URL编码。例如,使用curl并使用application/x-www-form-urlencoded MIME类型:

Lua 复制代码
curl -i -X POST http://localhost:8001/routes \
 --data-urlencode 'uris[]=/status/\d+'

响应:

Lua 复制代码
HTTP/1.1 201 Created
...

请注意,curl不会自动URL编码您的有效负载,并且请注意使用--data-urlencode,它防止+字符被URL解码并被Kong Gateway的Admin API解释为空格 ` `。

strip_path属性

为了在匹配路由时指定路径前缀,但不在上游请求中包含该前缀,可以使用strip_path布尔属性通过配置路由来实现:

Lua 复制代码
{
   "paths": ["/service"],
   "strip_path": true,
   "service": {
       "id": "..."
   }
}

启用此标志会告诉Kong Gateway,在匹配此路由并代理至服务时,不应在上游请求的URL中包含URL路径的匹配部分。例如,对于上述路由的以下客户端请求:

Lua 复制代码
GET /service/path/to/resource HTTP/1.1
Host: ...

这将导致Kong Gateway发送以下的上游请求:

Lua 复制代码
GET /path/to/resource HTTP/1.1
Host: ...

同样地,如果在已启用strip_path的路由上定义了一个正则表达式路径,则会剥离请求URL匹配序列的全部内容。例如:

Lua 复制代码
{
   "paths": ["/version/\d+/service"],
   "strip_path": true,
   "service": {
       "id": "..."
   }
}

匹配所提供的正则表达式路径的以下HTTP请求:

Lua 复制代码
GET /version/1/service/path/to/resource HTTP/1.1
Host: ...

由Kong Gateway作为上游代理发送的请求为:

Lua 复制代码
GET /path/to/resource HTTP/1.1
Host: ...

规范化行为

为了防止绕过常规路由匹配,客户端发来的请求URI在路由匹配前根据RFC 3986进行规范化。具体而言,对于传入的请求URI会使用以下规范化技术,这些技术被选择是因为它们通常不改变请求URI的语义:

  1. 百分号编码的三元组将转换为大写。例如:/foo%3a 转换为 /foo%3A。

  2. 百分号编码的非保留字符将被解码。例如:/fo%6F 解码为 /foo。

  3. 必要时删除点段(dot segments)。例如:/foo/./bar/../baz 删除为 /foo/baz。

  4. 合并重复斜杠。例如:/foo//bar 合并为 /foo/bar。

Route对象的paths属性也被规范化。首先确定路径是普通文本还是正则表达式路径,然后使用不同的规范化技术。

对于普通文本路由路径:

与上述相同的规范化技术被使用,即方法1到4。

对于正则表达式路由路径:

只使用方法1和2。此外,如果解码后的字符成为正则表达式元字符,将使用反斜杠进行转义。

Kong Gateway在执行路由匹配之前将规范化任何传入的请求URI。因此,发送到上游服务的任何请求URI也将保持规范化形式,以保留原始URI的语义。

请求的HTTP方法

methods字段允许根据请求的HTTP方法进行匹配。它可以接受多个值。默认值为空(不使用HTTP方法进行路由)。

以下路由允许通过GET和HEAD进行路由:

Lua 复制代码
{
   "methods": ["GET", "HEAD"],
   "service": {
       "id": "..."
   }
}

这样的路由将与以下请求匹配:

Lua 复制代码
GET / HTTP/1.1
Host: ...
Lua 复制代码
HEAD /resource HTTP/1.1
Host: ...

但它不会与POST或DELETE请求匹配。这在配置路由上允许更精确的细粒度控制。例如,可以想象有两个指向相同服务的路由:一个允许无限制的非身份验证GET请求,另一个只允许通过身份验证和速率限制的POST请求(通过将身份验证和速率限制插件应用于这些请求)。

请求来源

注意:此部分仅适用于TCP和TLS路由。

sources路由属性允许通过一组传入连接的IP和/或端口源来匹配路由。

以下路由允许通过一组源IP/端口进行路由:

Lua 复制代码
{
   "protocols": ["tcp", "tls"],
   "sources": [{"ip":"10.1.0.0/16", "port":1234}, {"ip":"10.2.2.2"}, {"port":9123}],
   "id": "...",
}

来自CIDR范围为"10.1.0.0/16"或IP地址为"10.2.2.2"或端口为"9123"的TCP或TLS连接将匹配此路由。

请求目标

注意:本部分仅适用于TCP和TLS路由。

类似于sources属性,destinations属性允许通过传入连接的IP和/或端口的列表来匹配路由,但使用TCP/TLS连接的目标作为路由属性。

请求 SNI

当使用安全协议(如https、grpcs或tls)时,可以使用服务器名称指示作为路由属性。以下路由允许通过SNIs进行路由选择:

Lua 复制代码
{
 "snis": ["foo.test", "example.com"],
 "id": "..."
}

与TLS连接的SNI扩展中设置的匹配主机名的传入请求将被路由到此路由。如前所述,SNI路由不仅适用于TLS,还适用于通过TLS传输的其他协议,例如HTTPS等。如果在路由中指定了多个SNI,则其中任何一个都可以与传入请求的SNI匹配(名称之间使用OR关系)。

SNI在TLS握手时指示,并且在建立TLS连接后无法修改。这意味着,例如,多个重复使用相同keepalive连接的请求在执行路由匹配时将具有相同的SNI主机名,而不考虑Host头信息。

也就是说,在执行路由匹配时,通过保持活动连接发送多个请求的情况下,这些请求将具有相同的SNI主机名(不考虑Host头信息)。

请注意,可以创建具有不匹配的SNI和Host头信息匹配器的路由,但通常不建议这样做。

匹配优先级

一条路由可以根据其头信息(headers)、主机名(hosts)、路径(paths)和方法(methods)定义匹配规则(对于安全路由还包括SNIs字段:"https"、"grpcs"、"tls")。为了使Kong Gateway能将传入请求与路由匹配,必须满足所有字段的条件。然而,Kong Gateway允许一定的灵活性,允许配置两个或多个具有相同值的字段的路由 - 当发生这种情况时,Kong Gateway应用优先级规则。

规则是:在评估请求时,Kong Gateway首先尝试匹配具有最多规则的路由。

例如,如果配置了两个路由如下:

Lua 复制代码
{
   "hosts": ["example.com"],
   "service": {
       "id": "..."
   }
},
{
   "hosts": ["example.com"],
   "methods": ["POST"],
   "service": {
       "id": "..."
   }
}

第二个路由有hosts字段和methods字段,所以Kong Gateway首先对其进行评估。通过这样做,我们避免了第一个路由"掩盖"了本应为第二个路由而来的请求。

因此,这个请求匹配了第一个路由:

Lua 复制代码
GET / HTTP/1.1
Host: example.com

而这个请求匹配了第二个路由:

Lua 复制代码
POST / HTTP/1.1
Host: example.com

按照这个逻辑,如果配置了第三个路由,该路由具有hosts字段、methods字段和uris字段,它将首先由Kong Gateway进行评估。

如果两个路由A和B在给定请求中的规则数量相同,则将按照以下列出的顺序应用下面的决胜规则。如果符合以下条件之一,将选择路由A而不是B:

  • 路由A只有"普通"主机头(headers),而路由B有一个或多个"通配符"主机头

  • 路由A具有比路由B更多的非主机头(headers)

  • 路由A至少有一个"正则表达式"路径(paths),而路由B只有"普通"路径

  • A中最长的路径比B中最长的路径更长

  • A.created_at < B.created_at (创建时间早于B)

代理行为

上述的代理规则详细说明了Kong Gateway如何将传入的请求转发给上游服务。下面,我们将详细介绍Kong Gateway在匹配HTTP请求与注册路由之间以及实际转发请求之间发生的内部过程。

负载均衡

Kong Gateway实现了负载均衡功能,以将被代理的请求分配到上游服务的一组实例之间。

插件执行

Kong Gateway通过"插件"进行扩展,这些插件将自身挂接在被代理请求的请求/响应生命周期中。插件可以在您的环境中执行各种操作,并对被代理请求进行转换。

插件可以配置为全局运行(对所有被代理流量)或在特定路由和服务上运行。在这两种情况下,您都必须通过Admin API创建插件配置。

一旦匹配了路由(以及其关联的服务实体),Kong Gateway将运行与这些实体相关联的插件。在路由上配置的插件会在服务上配置的插件之前运行,但除此之外,插件关联的常规规则仍适用。

这些配置的插件将运行它们的访问阶段,您可以在插件开发指南中找到有关此阶段的更多信息。

代理和上游(upstream )超时

一旦Kong Gateway执行了所有必要的逻辑(包括插件),它就准备将请求转发给上游服务。这是通过Nginx的ngx_http_proxy_module来实现的。您可以通过以下服务属性来配置Kong Gateway与特定上游之间的连接超时时间:

  • connect_timeout:定义与上游服务建立连接的超时时间(以毫秒为单位)。默认为60000毫秒。
  • write_timeout:定义传输请求到上游服务的两次连续写操作之间的超时时间(以毫秒为单位)。默认为60000毫秒。
  • read_timeout:定义接收来自上游服务的请求的两次连续读取操作之间的超时时间(以毫秒为单位)。默认为60000毫秒。

Kong Gateway将使用HTTP/1.1发送请求,并设置以下头部信息:

  • Host: <your_upstream_host>,如文档中先前描述的那样。
  • Connection: keep-alive,以允许复用上游连接。
  • X-Real-IP: <remote_addr>,其中remote_addr是ngx_http_core_module提供的同名变量。请注意,remote_addr可能被ngx_http_realip_module覆盖。
  • X-Forwarded-For: <address>,其中<address>是由ngx_http_realip_module提供的$realip_remote_addr的内容,附加到具有相同名称的请求头中。
  • X-Forwarded-Proto: <protocol>,其中<protocol>是客户端使用的协议。如果realip_remote_addr是受信任的地址之一,则转发具有相同名称的请求头(如果提供)。否则,将使用ngx_http_core_module提供的scheme变量的值。
  • X-Forwarded-Host: <host>,其中<host>是客户端发送的主机名。如果realip_remote_addr是受信任的地址之一,则转发具有相同名称的请求头(如果提供)。否则,将使用ngx_http_core_module提供的host变量的值。
  • X-Forwarded-Port: <port>,其中<port>是接受请求的服务器的端口。如果realip_remote_addr是受信任的地址之一,则转发具有相同名称的请求头(如果提供)。否则,将使用ngx_http_core_module提供的server_port变量的值。
  • X-Forwarded-Prefix: <path>,其中<path>是由Kong Gateway接受的请求的路径。如果realip_remote_addr是受信任的地址之一,则转发具有相同名称的请求头(如果提供)。否则,将使用ngx_http_core_module提供的request_uri变量的值(去除查询字符串)。

注意:Kong Gateway将对空路径返回"/",但不会对请求路径进行其他规范化处理。

Kong Gateway会将所有其他请求头按原样转发。

唯一的例外是在使用WebSocket协议时。如果是这样,Kong Gateway将设置以下头部以允许在客户端和上游服务之间升级协议:

  • Connection: Upgrade
  • Upgrade: websocket
错误和重试

每当在代理过程中发生错误时,Kong Gateway会使用底层的Nginx重试机制将请求传递给下一个上游服务。

这里有两个可配置的元素:

  • 重试次数:可以针对每个服务使用retries属性进行配置。有关详细信息,请参阅管理API。
  • 什么样的情况被视为错误:这里Kong Gateway使用的是Nginx的默认设置,这意味着在与服务器建立连接、发送请求或读取响应头时发生错误或超时。

第二个选项是基于Nginx的proxy_next_upstream指令。这个选项不能直接通过Kong Gateway进行配置,但可以通过自定义Nginx配置进行添加。

响应

Kong Gateway从上游服务接收响应,并以流式传输的方式将其发送回下游客户端。此时,Kong Gateway执行添加到路由和/或服务中的后续插件,在header_filter阶段实现钩子功能。

一旦所有注册的插件的`header_filter`阶段被执行完毕,Kong Gateway会添加以下头部并将完整的头部集合发送给客户端:

  • Via: kong/x.x.x,其中x.x.x是所使用的Kong Gateway版本号。
  • X-Kong-Proxy-Latency: <latency>,其中latency是Kong Gateway从客户端接收请求到将请求发送给上游服务所经过的时间,以毫秒为单位。
  • X-Kong-Upstream-Latency: <latency>,其中latency是Kong Gateway等待上游服务响应的第一个字节所经过的时间,以毫秒为单位。

一旦头部被发送给客户端,Kong Gateway开始执行为路由和/或服务注册的插件,这些插件实现了`body_filter`钩子。由于Nginx的流式传输特性,该钩子可能会被调用多次。每个成功通过`body_filter`钩子处理的上游响应块都会被发送回客户端。您可以在插件开发指南中找到有关`body_filter`钩子的更多信息。

配置回退路由

作为Kong Gateway代理能力提供的灵活性的实际用例和示例,让我们尝试实现一个"回退路由",以便在避免Kong Gateway响应HTTP 404 "找不到路由"的情况下,可以捕获此类请求并将它们代理到特殊的上游服务,或者对其应用插件(例如,此类插件可以使用不同的状态码终止请求或在不代理请求的情况下响应)。

下面是一个这样的回退路由示例:

Lua 复制代码
{
   "paths": ["/"],
   "service": {
       "id": "..."
   }
}

正如您所猜测的,由于所有URI都以根字符/为前缀,所以所有发送到Kong Gateway的HTTP请求实际上都会匹配此路由。正如我们从请求路径部分了解到的,Kong Gateway优先评估最长的URL路径,因此/路径将最后被Kong Gateway评估,并有效提供一个"回退"路由,只作为最后的备选方案进行匹配。然后,您可以将流量发送到特殊服务或在该路由上应用任何您希望的插件。

为路由配置TLS

Kong Gateway提供了一种按连接动态提供TLS证书的方式。TLS证书由核心直接处理,并通过Admin API进行配置。连接到Kong Gateway的TLS客户端必须支持服务器名称指示扩展以使用此功能。

TLS证书在Kong Gateway Admin API中由两个资源处理:

  • /certificates,用于存储您的密钥和证书。
  • /snis,将注册的证书与服务器名称指示关联起来。

以下是如何在给定路由上配置TLS证书的步骤:首先,通过Admin API上传您的TLS证书和密钥:

Lua 复制代码
curl -i -X POST http://localhost:8001/certificates \
 -F "cert=@/path/to/cert.pem" \
 -F "key=@/path/to/cert.key" \
 -F "snis=*.tls-example.com,other-tls-example.com"

响应:

Lua 复制代码
HTTP/1.1 201 Created
...

snis表单参数是一个简化参数,直接插入一个SNI并将上传的证书与其关联。

请注意,snis中定义的一个SNI名称包含一个通配符(*.tls-example.com)。在最左边(前缀)或最右边(后缀)位置,SNI可以包含一个单个通配符。当维护多个子域时,这可能很有用。配置了一个带有通配符名称的单个sni可以用来匹配多个子域,而不需要为每个子域创建一个SNI。

有效的通配符位置是mydomain.*、*.mydomain.com和*.www.mydomain.com。

可以使用以下参数在Kong Gateway配置中添加默认证书:

  • ssl_cert
  • ssl_cert_key

或者,通过动态配置具有*的SNI的默认证书:

Lua 复制代码
curl -i -X POST http://localhost:8001/certificates \
 -F "cert=@/path/to/default-cert.pem" \
 -F "key=@/path/to/default-cert.key" \
 -F "snis=*"

响应:

Lua 复制代码
HTTP/1.1 201 Created
...

snis的匹配遵循以下优先级:

  1. 精确的SNI匹配证书
  2. 按前缀通配符搜索证书
  3. 按后缀通配符搜索证书
  4. 搜索与SNI * 关联的证书
  5. 文件系统上的默认证书

您现在必须在Kong Gateway中注册以下路由。为了方便起见,我们只使用Host标头来匹配对该路由的请求:

Lua 复制代码
curl -i -X POST http://localhost:8001/routes \
 -d 'hosts=prefix.tls-example.com,other-tls-example.com' \
 -d 'service.id=d54da06c-d69f-4910-8896-915c63c270cd'

响应:

Lua 复制代码
HTTP/1.1 201 Created
...

您现在可以期望该路由通过Kong Gateway以HTTPS方式提供服务:

Lua 复制代码
curl -i https://localhost:8443/ \
 -H "Host: prefix.tls-example.com"

响应:

Lua 复制代码
HTTP/1.1 200 OK
...

在建立连接和进行TLS握手时,如果您的客户端在SNI扩展中发送prefix.tls-example.com,Kong Gateway将提供先前配置的cert.pem证书。无论是HTTPS还是TLS连接都是如此。

限制客户端协议

路由具有protocols属性,用于限制应该监听的客户端协议。该属性接受一组值,可以是 "http"、"https"、"grpc"、"grpcs"、"tcp" 或 "tls"。

具有 http 和 https 的路由将接受两种协议的流量。

Lua 复制代码
{
   "hosts": ["..."],
   "paths": ["..."],
   "methods": ["..."],
   "protocols": ["http", "https"],
   "service": {
       "id": "..."
   }
}

不指定任何协议具有相同的效果,因为路由默认为 ["http", "https"]。

但是,只有 https 的路由将仅接受通过HTTPS的流量。如果之前发生了来自受信任IP的TLS终止,则它也将接受未加密的流量。当请求来自于trusted_ips中配置的IP之一,并且设置了X-Forwarded-Proto: https头时,认为TLS终止是有效的。

Lua 复制代码
{
   "hosts": ["..."],
   "paths": ["..."],
   "methods": ["..."],
   "protocols": ["https"],
   "service": {
       "id": "..."
   }
}

如果上述那样的一个路由匹配了一个请求,但该请求是明文传输且没有通过有效的TLS终止,Kong Gateway会回复以下内容:

Lua 复制代码
HTTP/1.1 426 Upgrade Required
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: Upgrade
Upgrade: TLS/1.2, HTTP/1.1
Server: kong/x.y.z

{"message":"Please use HTTPS protocol"}

可以通过在protocols属性中使用"tcp"来创建针对原始TCP(不一定是HTTP)连接的路由:

Lua 复制代码
{
   "hosts": ["..."],
   "paths": ["..."],
   "methods": ["..."],
   "protocols": ["tcp"],
   "service": {
       "id": "..."
   }
}

类似地,我们可以使用"tls"值创建接受原始TLS流量(不一定是HTTPS)的路由:

Lua 复制代码
{
   "hosts": ["..."],
   "paths": ["..."],
   "methods": ["..."],
   "protocols": ["tls"],
   "service": {
       "id": "..."
   }
}

只有 TLS 的路由将仅接受 TLS 流量。

还可以同时接受 TCP 和 TLS 流量:

Lua 复制代码
{
   "hosts": ["..."],
   "paths": ["..."],
   "methods": ["..."],
   "protocols": ["tcp", "tls"],
   "service": {
       "id": "..."
   }
}

要使L4 TLS代理正常工作,需要创建一个接受tls协议的路由,并上传适当的TLS证书,并将其sni属性正确设置为匹配传入连接的SNI。请参考上面的"为路由配置TLS"部分中的说明来进行设置。

代理WebSocket流量

Kong Gateway通过底层的Nginx实现来支持WebSocket流量。当您想要通过Kong Gateway在客户端和上游服务之间建立WebSocket连接时,必须进行WebSocket握手。这是通过HTTP升级机制完成的。以下是您的客户端对Kong Gateway发出的请求的示例:

Lua 复制代码
GET / HTTP/1.1
Connection: Upgrade
Host: my-websocket-api.com
Upgrade: WebSocket

这使得Kong Gateway将连接(Connection)和升级(Upgrade)头部转发给您的上游服务,而不是由于标准HTTP代理的逐跳特性而将其丢弃。

WebSocket代理模式

Kong Gateway上有两种代理WebSocket流量的方法:

  1. HTTP(S)方式的服务和路由

  2. WS(S)方式的服务和路由

HTTP(S)服务和路由

使用http和https协议的服务和路由完全能够处理WebSocket连接,无需特殊配置。使用此方法,WebSocket会话的行为与常规的HTTP请求完全相同,所有的请求/响应数据都被视为一个不透明的字节流。

Lua 复制代码
services:
 - name: my-http-websocket-service
   protocol: http
   host: 1.2.3.4
   port: 80
   path: /
   routes:
     - name: my-http-websocket-route
       protocols:
         - http
         - https

WS(S)服务和路由(企业版)

除了HTTP服务和路由之外,Kong Gateway Enterprise 还包括ws(WebSocket-over-http)和wss(WebSocket-over-https)选项作为服务协议和路由协议。与http/https不同,ws和wss服务可以完全控制底层的WebSocket连接。这意味着它们可以使用WebSocket插件和WebSocket PDK来执行基于每个消息的业务逻辑(消息验证、计费、速率限制等)。

Lua 复制代码
services:
 - name: my-dedicated-websocket-service
   protocol: ws
   host: 1.2.3.4
   port: 80
   path: /
   routes:
     - name: my-dedicated-websocket-route
       protocols:
         - ws
         - wss

注意:与http(s)服务的协议无关行为相比,解码和编码WebSocket消息会增加一定的性能开销。如果您的API不需要ws(s)服务提供的额外功能,建议您改用http(s)服务。

WebSocket 和 TLS

不论使用哪种服务/路由协议(http(s)或ws(s)),Kong Gateway都将在相应的http和https端口上接受普通和TLS的WebSocket连接。要强制客户端使用TLS连接,请将路由的protocols属性设置为仅限https或wss。

在设置服务以指向上游WebSocket服务时,您应该仔细选择Kong Gateway与上游之间要使用的协议。

如果您想使用TLS,您的上游WebSocket服务必须在服务协议属性中使用https(或wss)协议,并使用正确的端口(通常为443)。如果想不使用TLS连接,则应使用http(或ws)协议和端口(通常为80)。

如果您希望Kong Gateway终止TLS连接,可以仅接受来自客户端的https/wss连接,并在明文(http或ws)上代理到上游服务。

代理gRPC流量

Kong Gateway原生支持gRPC代理。为了管理gRPC服务并通过Kong Gateway代理gRPC请求,需要为gRPC服务创建服务和路由。

目前仅支持与gRPC一起使用的可观测性和日志记录插件。在Kong Hub页面上,可以在支持的协议字段中找到"grpc"和"grpcs",例如File Log插件的页面。

代理TCP/TLS流量

Kong Gateway原生支持TCP和TLS代理。

在这种模式下,达到stream_listen端点的传入连接的数据将被传递到上游。还可以使用此模式终止来自客户端的TLS连接。

要使用此模式,除了定义stream_listen之外,还需要创建带有协议类型为tcp或tls的适当路由/服务对象。

如果希望Kong Gateway终止TLS连接,则必须满足以下条件:

  1. Kong Gateway上用于TLS连接的端口必须启用ssl标志
  2. Kong Gateway内部必须存在可用于TLS终止的证书/密钥,如在为路由配置TLS中所示。

Kong Gateway将使用连接客户端的TLS SNI服务器名称扩展来找到适用的TLS证书。

在服务端,取决于Kong Gateway和上游服务之间的连接是否需要加密,可以相应地设置tcp或tls协议类型。这意味着在这种模式下支持以下所有设置:

  1. 客户端 <- TLS -> Kong <- TLS -> 上游
  2. 客户端 <- TLS -> Kong <- 明文 -> 上游
  3. 客户端 <- 明文 -> Kong <- TLS -> 上游

代理TLS透传流量

Kong Gateway支持代理TLS请求而无需终止或称为SNI代理。

Kong Gateway使用连接客户端的TLS SNI扩展来查找匹配的路由和服务,并将完整的TLS请求转发到上游,而不解密传入的TLS流量。

在这种模式下,您需要执行以下操作:

  • 创建一个使用协议"tls_passthrough"的路由对象,并将snis字段设置为一个或多个SNIs。
  • 将相应的服务对象的协议设置为"tcp"。
  • 将请求发送到具有流监听指令中ssl标志的端口。

不需要单独的SNI和证书实体,也不会使用它们。

路由不允许同时匹配tls和tls_passthrough协议。然而,相同的SNI可以在不同的路由中同时匹配tls和tls_passthrough协议。

还可以将路由设置为tls_passthrough,服务设置为tls。在这种模式下,与上游的连接将进行两次TLS加密。

注意:要在此模式下运行任何插件,插件的protocols字段必须包括tls_passthrough。

相关推荐
莱茵不哈哈1 天前
OpenResty 深度解析:构建高性能 Web 服务的终极方案
nginx·lua·kong·openresty·conf
泽济天下1 天前
【工作记录】Kong Gateway入门篇之简介
gateway·kong
SHUIPING_YANG2 天前
Nginx 返回 504 状态码表示 网关超时(Gateway Timeout)原因排查
运维·nginx·gateway
Volunteer Technology3 天前
SpringCloud Gateway知识点整理和全局过滤器实现
spring·spring cloud·gateway
matrixlzp3 天前
K8S Gateway AB测试、蓝绿发布、金丝雀(灰度)发布
kubernetes·gateway·ab测试
泽济天下5 天前
【工作记录】Kong Gateway 入门篇之部署及简单测试
gateway·kong
JAVA坚守者5 天前
API 网关核心功能解析:负载均衡、容灾、削峰降级原理与实战摘要
gateway·负载均衡·微服务架构·容灾备份·api 网关·削峰降级·云原生技术
大G哥6 天前
实战演练:用 AWS Lambda 和 API Gateway 构建你的第一个 Serverless API
云原生·serverless·云计算·gateway·aws
说淑人7 天前
Spring Cloud & 以Gateway实现限流(自定义返回内容)
java·spring cloud·gateway·限流
zhojiew8 天前
istio in action之Gateway流量入口与安全
安全·gateway·istio