介绍
在本文中,我们将通过详细解释Kong的路由功能和内部工作原理来介绍它的代理功能。
Kong公开了几个接口,可以通过两个配置属性进行调整:
proxy_listen,它定义了一个地址/端口列表,Kong将在这些地址/端口上接受来自客户机的公共流量,并将其代理到您的上游服务(默认情况下为8000)。
admin_listen,它还定义了一个地址和端口列表,但是这些地址和端口应该限制为仅供管理员访问,因为它们公开了Kong的配置功能:Admin API(默认情况下是8001)。
注意:从1.0.0开始,API实体已经被删除。本文档将介绍与新路由和服务实体的代理。
如果您使用0.12或更低版本,请参阅此文档的旧版本。
术语
client:指下游客户端对Kong代理端口的请求。
upstream service:指位于Kong后面的您自己的API/服务,客户端请求被转发的最终目的地。nginx中的上游服务器。
Service:顾名思义,服务实体是每个上游服务的抽象。服务的示例包括数据转换微服务、计费API等。
Route:指Kong路由实体。路由是进入Kong的入口点,定义要匹配的请求的规则,并路由到给定的服务。
Plugin:这是指Kong“插件”,它是在代理生命周期中运行的业务逻辑。插件可以通过管理API配置——全局(所有传入的流量)或特定的路由和服务。
概述
从高层次的角度来看,Kong在其配置的代理端口(默认情况下为8000和8443)上侦听HTTP流量。Kong将根据您配置的路由评估任何传入的HTTP请求,并尝试找到匹配的路由。如果给定的请求符合特定路由的规则,Kong将处理代理请求。因为每个路由都链接到一个服务,所以Kong将运行您在路由及其相关服务上配置的插件,然后在上游代理请求。
您可以通过Kong的管理API管理路由。路由的主机、路径和方法属性定义匹配传入HTTP请求的规则。
如果Kong收到一个无法与任何已配置路由匹配的请求(或未配置路由),它将以以下方式响应:
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添加一个Service
配置服务快速入门指南解释了如何通过管理API配置Kong。
向Kong添加服务是通过向Admin API发送HTTP请求来完成的:
curl -i -X POST http://localhost:8001/services/ \ -d 'name=foo-service' \ -d 'url=http://foo-service.com' 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注册一个名为“foo-service”的服务,该服务指向http://foo-service.com(您的上游)。
注意:url参数。这里至少有一个protocol、host、port和path参数。
现在,为了通过Kong向该服务发送流量,我们需要指定一条路由,它作为到Kong的入口点:
curl -i -X POST http://localhost:8001/routes/ \ -d 'hosts[]=example.com' \ -d 'paths[]=/foo' \ -d 'service.id=d54da06c-d69f-4910-8896-915c63c270cd' 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 }
我们现在已经配置了一条路由来匹配匹配给定主机和路径的传入请求,并将它们转发到我们配置的fo-service,从而将此流量代理到http://fo-service.com。
Kong是一个透明的代理,默认情况下,它会将请求转发到您的上游服务,不受影响,但HTTP规范所需的各种头文件(如Connection
, Date
等)除外。
路由和匹配能力
现在让我们讨论Kong如何根据路由的配置host、path和method属性(或字段)匹配请求。请注意,这三个字段都是可选的,但是必须指定其中至少一个字段。
对于匹配路由的请求:
- 请求必须包括所有配置的字段
- 请求中字段的值必须至少匹配一个配置值(虽然字段配置接受一个或多个值,但是一个请求只需要其中一个值被认为是匹配的)
让我们来看几个例子。考虑这样配置的路由:
{ "hosts": ["example.com", "foo-service.com"], "paths": ["/foo", "/bar"], "methods": ["GET"] }
匹配此路由的一些可能的请求如下:
GET /foo HTTP/1.1 Host: example.com GET /bar HTTP/1.1 Host: foo-service.com GET /foo/hello/world HTTP/1.1 Host: example.com
这三个请求都满足路由定义中设置的所有条件。
但是,以下请求与配置的条件不匹配:
GET / HTTP/1.1 Host: example.com POST /foo HTTP/1.1 Host: example.com GET /foo HTTP/1.1 Host: foo.com
所有这三个请求只满足两个配置条件。第一个请求的路径与任何配置的路径都不匹配,第二个请求的HTTP方法和第三个请求的主机标头也是如此。
现在我们已经了解了host、path和method属性是如何协同工作的,接下来让我们分别研究每个属性。
Request Host header
基于host header路由请求是通过Kong代理流量的最直接方法,特别是因为这是HTTP host header的预期用途。Kong使得通过Route实体的hosts字段很容易做到这一点。
host接受多个值,当通过管理API指定它们时,必须用逗号分隔:
host接受多个值,这在JSON有效负载中很容易表示:
curl -i -X POST http://localhost:8001/routes/ \ -H 'Content-Type: application/json' \ -d '{"hosts":["example.com", "foo-service.com"]}' HTTP/1.1 201 Created ...
但是由于Admin API也支持表单- urlencoding内容类型,您可以通过[]符号指定一个数组:
curl -i -X POST http://localhost:8001/routes/ \ -d 'hosts[]=example.com' \ -d 'hosts[]=foo-service.com' HTTP/1.1 201 Created ...
要满足此路由的host条件,来自客户机的任何传入请求现在必须将其Host header集设置为:
Host: example.com or: Host: foo-service.com
使用通配符的主机名(Using wildcard hostnames)
为了提供灵活性,Kong允许您在hosts字段中使用通配符指定主机名。通配符主机名允许任何匹配的主机标头满足条件,从而匹配给定的路由。
通配符主机名只能在域的最左边或最右边的标签上包含一个星号。例子:
- *.example.com将允许a.example.com和x.y.example.com等主机值匹配。
- example.*将允许example.com和example.org等主机值匹配。
一个完整的例子是这样的:
{ "hosts": ["*.example.com", "service.com"] }
这将使下列要求符合如下路由:
GET / HTTP/1.1 Host: an.example.com GET / HTTP/1.1 Host: service.com
preserve_host
属性
When proxying, Kong’s default behavior is to set the upstream request’s Host header to the hostname specified in the Service’s host
. The preserve_host
field accepts a boolean flag instructing Kong not to do so.
For example, when the preserve_host
property is not changed and a Route is configured like so:
在代理时,Kong的默认行为是将上游请求的Host header设置为Services中指定的host。preserve_host字段接受一个布尔值,指示Kong要不要这样做。
例如,如果没有更改preserve_host属性,并且路由的配置如下:
{ "hosts": ["service.com"], "service": { "id": "..." } }
客户可能会向kong提出以下要求:
GET / HTTP/1.1 Host: service.com
Kong将从Service的Host属性中提取Host header值,并发送以下上游请求:
GET / HTTP/1.1 Host: <my-service-host.com>
然而,通过显式配置带有preserve_host=true的路由:
{ "hosts": ["service.com"], "preserve_host": true, "service": { "id": "..." } }
并假设来自客户端的相同请求:
GET / HTTP/1.1 Host: service.com
Kong将保留客户端请求上的主机,并发送以下上游请求:
GET / HTTP/1.1 Host: service.com
Request path
路由匹配的另一种方式是通过请求paths。要满足此路由条件,客户端请求的路径必须以path属性的一个值作为前缀。
例如,路由配置如下:
{
"paths": ["/service", "/hello/world"] }
The following requests would be matched:
GET /service HTTP/1.1 Host: example.com
GET /service/resource?param=value HTTP/1.1 Host: example.com
GET /hello/world/resource HTTP/1.1 Host: anything.com
对于每个请求,Kong都检测到它们的URL path是不是和route path的前缀。默认情况下,Kong将在不更改URL path的情况下代理上游的请求。
When proxying with path prefixes, the longest paths get evaluated first. This allow you to define two Routes with two paths: /service
and /service/resource
, and ensure that the former does not “shadow” the latter.
当代理路径前缀时,首先计算最长的路径。这允许您使用两条路径定义两条路由:/service和/service/resource,并确保前者不会“影响”后者。
Using regexes in paths
Kong通过PCRE (Perl兼容的正则表达式)支持route paths字段的正则表达式模式匹配。您可以同时使用前缀和正则表达式规则在paths参数上。
例如,如果我们考虑以下route:
{
"paths": ["/users/\d+/profile", "/following"] }
The following requests would be matched by this Route:
GET /following HTTP/1.1 Host: ...
GET /users/123/profile HTTP/1.1 Host: ...
提供的正则表达式使用a PCRE标志(pcre_anchor)计算,这意味着它们将被约束在路径中的第一个匹配点(根/字符)进行匹配。
Evaluation order(评估顺序:数字越大优先级越高)
如前所述,Kong按长度计算前缀路径:首先计算最长的前缀路径。但是,Kong将基于从最高优先级到最低优先级路由的regex_priority属性来计算regex路径。这意味着考虑以下routes:
[
{
"paths": ["/status/\d+"], "regex_priority": 0 }, { "paths": ["/version/\d+/status/\d+"], "regex_priority": 6 }, { "paths": ["/version"], }, { "paths": ["/version/any/"], } ]
In this scenario, Kong will evaluate incoming requests against the following defined URIs, in this order:
/version/any/
/version
/version/\d+/status/\d+
/status/\d+
前缀路径总是在regex路径之前计算。
通常,请求还必须匹配路由Route’s hosts
and methods
属性,Kong将遍历您的路由,直到找到与大多数规则匹配的一个(参见[路由优先级][proxy- Routing - priority])。
Capturing groups
还支持捕捉组,匹配的组将从路径中提取出来,供插件使用。如果我们考虑以下正则表达式:
/version/(?<version>\d+)/users/(?<user>\S+)
And the following request path:
/version/1/users/john
Kong将把请求路径视为匹配的,如果总体路由匹配(考虑host和method字段),则提取的捕获组将可从ngx中的插件中获得。ctx变量:
local router_matches = ngx.ctx.router_matches -- router_matches.uri_captures is: -- { "1", "john", version = "1", user = "john" }
Escaping special characters(转义特殊字符)
curl -i -X POST http://localhost:8001/routes \
--data-urlencode 'uris[]=/status/\d+'
HTTP/1.1 201 Created
...
请注意,curl不会自动对有效负载进行URL编码,并注意——data-urlencode的使用,它阻止+字符被Kong的管理API解码并解释为空格' '。
The strip_path
property
最好指定一个路径前缀来匹配某个路由,但不要将其包含在上游请求中。为此,可以使用strip_path布尔属性来配置这样的路由:
{
"paths": ["/service"], "strip_path": true, "service": { "id": "..." } }
启用此标志指示Kong在匹配此路由并继续代理服务时,不应将URL路径的匹配部分包含在上游请求的URL中。例如,以下客户对上述路由的请求:
GET /service/path/to/resource HTTP/1.1 Host: ...
Will cause Kong to send the following upstream request:
GET /path/to/resource HTTP/1.1 Host: ...
同样,如果在启用了strip_path的路由上定义了regex路径,则将删除请求URL匹配序列的全部内容。例子:
{
"paths": ["/version/\d+/service"], "strip_path": true, "service": { "id": "..." } }
The following HTTP request matching the provided regex path:
GET /version/1/service/path/to/resource HTTP/1.1 Host: ...
Will be proxied upstream by Kong as:
GET /path/to/resource HTTP/1.1 Host: ...
Request HTTP method
methods字段允许根据HTTP方法匹配请求。它接受多个值。它的默认值是空的(HTTP方法不用于路由)。
The following Route allows routing via GET
and HEAD
:
{
"methods": ["GET", "HEAD"], "service": { "id": "..." } }
Such a Route would be matched with the following requests:
GET / HTTP/1.1 Host: ...
HEAD /resource HTTP/1.1 Host: ...
但它不匹配POST或DELETE请求。这允许在路由上配置插件时使用更细的粒度。例如,可以想象指向相同服务的两条路由:一条是无限制的未经身份验证的GET请求,另一条是只允许经过身份验证和速率限制的POST请求(通过对此类请求应用身份验证和速率限制插件)。
Matching priorities
路由可以基于其host、path和methods字段定义匹配规则。要使传入请求与路由匹配,必须满足所有现有字段。但是,Kong允许使用包含相同值的字段配置两个或多个路由,从而提供了相当大的灵活性——当出现这种情况时,Kong应用优先级规则。
The rule is: when evaluating a request, Kong will first try to match the Routes with the most rules.
For example, if two Routes are configured like so:
{
"hosts": ["example.com"], "service": { "id": "..." } }, { "hosts": ["example.com"], "methods": ["POST"], "service": { "id": "..." } }
The second Route has a hosts
field and a methods
field, 因此第二条规则优先被Kong进行评估。通过这样做,我们避免了第一个路由对第二个路由的“阴影”调用。
Thus, this request will match the first Route
GET / HTTP/1.1 Host: example.com
And this request will match the second one:
POST / HTTP/1.1 Host: example.com
按照这种逻辑,如果要为第三条路由配置host字段、methods字段和uri字段,那么首先由Kong对其进行评估。
Proxying behavior
上面的代理规则详细说明了Kong如何将传入的请求转发给上游服务。下面,我们将详细说明从Kong匹配具有注册路由的HTTP请求到实际转发请求之间的内部过程。
1. Load balancing
Kong实现了负载平衡功能,可以跨上游服务的实例池分发代理请求。
You can find more information about configuring load balancing by consulting the Load Balancing Reference.
2. Plugins execution
Kong可以通过“插件”进行扩展,这些插件将自己挂接到代理请求的请求/响应生命周期中。插件可以在您的环境中对代理请求执行各种操作和/或转换。
插件可以配置为全局运行(针对所有代理的流量)或在特定的路由和服务上运行。在这两种情况下,您必须通过管理API创建plugin configuration。
一旦匹配了路由(及其关联的服务实体),Kong将运行与这两个实体关联的插件。在路由上配置的插件在服务上配置的插件之前运行,但是在其他情况下,应用插件关联plugins association的通常规则。
这些配置好的插件将运行它们的访问阶段,您可以在插件开发指南Plugin development guide中找到更多关于这个阶段的信息。
3. Proxying & upstream timeouts
一旦Kong执行了所有必要的逻辑(包括插件),它就可以将请求转发给上游服务了。这是通过Nginx的ngx_http_proxy_module完成的。您可以通过服务的以下属性为Kong和给定上游之间的连接配置所需的超时:
upstream_connect_timeout:以毫秒为单位定义建立到上游服务的连接的超时。默认为60000。
upstream_send_timeout:以毫秒为单位定义用于将请求发送到上游服务的两个连续写操作之间的超时。默认为60000。
upstream_read_timeout:以毫秒为单位定义用于接收上游服务请求的两个连续读操作之间的超时。默认为60000。
Kong will send the request over HTTP/1.1, and set the following headers:
Host: <your_upstream_host>
, as previously described in this document.Connection: keep-alive
, to allow for reusing the upstream connections.X-Real-IP: <remote_addr>
, where$remote_addr
is the variable bearing the same name provided by ngx_http_core_module. Please note that the$remote_addr
is likely overridden by ngx_http_realip_module.X-Forwarded-For: <address>
, where<address>
is the content of$realip_remote_addr
provided by ngx_http_realip_module appended to the request header with the same name.X-Forwarded-Proto: <protocol>
, where<protocol>
is the protocol used by the client. In the case where$realip_remote_addr
is one of the trusted addresses, the request header with the same name gets forwarded if provided. Otherwise, the value of the$scheme
variable provided by ngx_http_core_module will be used.X-Forwarded-Host: <host>
, where<host>
is the host name sent by the client. In the case where$realip_remote_addr
is one of the trusted addresses, the request header with the same name gets forwarded if provided. Otherwise, the value of the$host
variable provided by ngx_http_core_module will be used.X-Forwarded-Port: <port>
, where<port>
is the port of the server which accepted a request. In the case where$realip_remote_addr
is one of the trusted addresses, the request header with the same name gets forwarded if provided. Otherwise, the value of the$server_port
variable provided by ngx_http_core_module will be used.
All the other request headers are forwarded as-is by Kong.
One exception to this is made when using the WebSocket protocol. If so, Kong will set the following headers to allow for upgrading the protocol between the client and your upstream services:
Connection: Upgrade
Upgrade: websocket
More information on this topic is covered in the [Proxy WebSocket traffic][proxy-websocket] section.
4. Errors & retries
当代理过程中发生错误时,Kong将使用底层的Nginx重试机制将请求传递到下一个上游。
这里有两个可配置的元素:
重试次数:可以使用重试属性为每个服务配置重试次数。有关这方面的详细信息,请参见管理API。
错误的确切构成:这里Kong使用Nginx默认值,这意味着在与服务器建立连接、向其传递请求或读取响应头时发生错误或超时。
第二个选项基于Nginx的[proxy_next_upstream][proxy_next_upstream]指令。此选项不能通过Kong直接配置,但可以使用自定义Nginx配置添加。 See the configuration reference for more details.
5. Response
Kong接收来自上游服务的响应,并以流方式将其发送回下游客户端。此时,Kong将执行添加到路由和/或服务的后续插件,这些插件在header_filter阶段实现一个钩子。
所有已注册插件的header_filter阶段执行完成后,Kong将添加以下header,并将完整的header发送给客户端:
Via: kong/x.x.x
, wherex.x.x
is the Kong version in useX-Kong-Proxy-Latency: <latency>
, wherelatency
is the time in milliseconds between Kong receiving the request from the client and sending the request to your upstream service.X-Kong-Upstream-Latency: <latency>
, wherelatency
is the time in milliseconds that Kong was waiting for the first byte of the upstream service response.
Once the headers are sent to the client, Kong will start executing registered plugins for the Route and/or Service that implement the body_filter
hook. This hook may be called multiple times, due to the streaming nature of Nginx. Each chunk of the upstream response that is successfully processed by such body_filter
hooks is sent back to the client. You can find more information about the body_filter
hook in the Plugin development guide.
Configuring a fallback Route
用一个实际用例说明Kong在提供灵活的代理服务中提供“fallback Route”,所以为了避免Kong返回HTTP 404响应、“no route found”,我们能赶上这样的请求和代理他们上游一个特殊的服务,或者应用插件(例如,这样一个插件可以终止请求用不同的状态代码没有代理请求或响应)。
Here is an example of such a fallback Route:
{
"paths": ["/"], "service": { "id": "..." } }
可以猜到,对Kong发出的任何HTTP请求实际上都与此路由匹配,因为所有uri都以根字符/作为前缀。正如我们在[请求路径][proxy-request-path]一节中所知道的,最长的URL路径首先由Kong计算,因此/ path最终将由Kong计算,并有效地提供了一个“fallback”路由,仅在最后才匹配。然后你可以发送流量到一个特殊的服务或应用任何插件,你希望在这条路线。
Configuring SSL for a Route
Kong提供了一种根据每个连接动态提供SSL证书的方法。SSL证书由核心直接处理,并通过管理API进行配置。连接到TLS上的Kong的客户机必须支持 Server Name Indication扩展才能使用此功能。
SSL证书由Kong管理API中的两个资源处理:
/certificates
, 它存储您的密钥和证书。/snis
, 将注册证书与服务器名称指示关联。
You can find the documentation for those two resources in the Admin API Reference.
Here is how to configure an SSL certificate on a given Route: first, upload your SSL certificate and key via the Admin API:
curl -i -X POST http://localhost:8001/certificates \
-F "cert=@/path/to/cert.pem" \
-F "key=@/path/to/cert.key" \ -F "snis=ssl-example.com,other-ssl-example.com" HTTP/1.1 201 Created ...
snis表单参数是sugar参数,直接插入SNI并将上传的证书关联到它。
你现在必须在Kong注册下列路线。为了方便,我们将仅使用host header将请求匹配到此路由:
curl -i -X POST http://localhost:8001/routes \
-d 'hosts=ssl-example.com,other-ssl-example.com' \
-d 'service.id=d54da06c-d69f-4910-8896-915c63c270cd'
HTTP/1.1 201 Created
...
You can now expect the Route to be served over HTTPS by Kong:
curl -i https://localhost:8443/ \
-H "Host: ssl-example.com"
HTTP/1.1 200 OK
...
在建立连接并协商SSL握手时,如果客户端发送SSL -example.com作为SNI扩展的一部分,Kong将提供之前配置的cert.pem证书。
Restricting the client protocol (HTTP/HTTPS/TCP/TLS)
路由有一个protocols属性来限制它们应该侦听的客户机协议。该属性接受一组值, which can be "http"
, "https"
, "tcp"
or "tls"
.
A Route with http
and https
will accept traffic in both protocols.
{
"hosts": ["..."], "paths": ["..."], "methods": ["..."], "protocols": ["http", "https"], "service": { "id": "..." } }
Not specifying any protocol has the same effect, since routes default to ["http", "https"]
.
然而,如果某个路由只配置了https那么就只接受https上的流量。如果SSL终止以前发生在受信任的IP上,它还将接受未加密的通信流。如果请求来自trusted_ips中配置的ip之一,并且设置了x -Forwarded-Proto: https
header,则认为SSL终止有效:
{
"hosts": ["..."], "paths": ["..."], "methods": ["..."], "protocols": ["https"], "service": { "id": "..." } }
If a Route such as the above matches a request, but that request is in plain-text without valid prior SSL termination, Kong responds with:
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"}
Since Kong 1.0 it’s possible to create routes for raw TCP (not necessarily HTTP) connections by using "tcp"
in the protocols
attribute:
{
"hosts": ["..."], "paths": ["..."], "methods": ["..."], "protocols": ["tcp"], "service": { "id": "..." } }
Similarly, we can create routes which accept raw TLS traffic (not necessarily HTTPS) with the "tls"
value:
{
"hosts": ["..."], "paths": ["..."], "methods": ["..."], "protocols": ["tls"], "service": { "id": "..." } }
A Route with only TLS
would only accept traffic over TLS.
It is also possible to accept both TCP and TLS simultaneously:
{
"hosts": ["..."], "paths": ["..."], "methods": ["..."], "protocols": ["tcp", "tls"], "service": { "id": "..." } }
Proxy WebSocket traffic
Kong supports WebSocket traffic thanks to the underlying Nginx implementation. When you wish to establish a WebSocket connection between a client and your upstream services through Kong, you must establish a WebSocket handshake. This is done via the HTTP Upgrade mechanism. This is what your client request made to Kong would look like:
GET / HTTP/1.1 Connection: Upgrade Host: my-websocket-api.com Upgrade: WebSocket
This will make Kong forward the Connection
and Upgrade
headers to your upstream service, instead of dismissing them due to the hop-by-hop nature of a standard HTTP proxy.
WebSocket and TLS
Kong will accept ws
and wss
connections on its respective http
and https
ports. To enforce TLS connections from clients, set the protocols
property of the Route to https
only.
When setting up the Service to point to your upstream WebSocket service, you should carefully pick the protocol you want to use between Kong and the upstream. If you want to use TLS (wss
), then the upstream WebSocket service must be defined using the https
protocol in the Service protocol
property, and the proper port (usually 443). To connect without TLS (ws
), then the http
protocol and port (usually 80) should be used in protocol
instead.
If you want Kong to terminate SSL/TLS, you can accept wss
only from the client, but proxy to the upstream service over plain text, or ws
.
Conclusion
Through this guide, we hope you gained knowledge of the underlying proxying mechanism of Kong, from how does a request match a Route to be routed to its associated Service, on to how to allow for using the WebSocket protocol or setup dynamic SSL certificates.
This website is Open-Source and can be found at github.com/Kong/docs.konghq.com. Feel free to provide feedback to this document there, or propose improvements!
If you haven’t already, we suggest that you also read the Load balancing Reference, as it closely relates to the topic we just covered.