API网关Kong实践笔记

Kong代理向导

2019-10-03  本文已影响0人  fossilman

简介

在这篇文档中,我们会通过详细介绍Kong的路由功能和内部工作原理来涵盖其代理功能,Kong通过两个配置项对外暴露代理相关的接口:

术语

概要

纵观全局,Kong服务监听其配置代理端口的Http流量(默认为 80008443 端口),然后根据用户配置的路由尝试匹配流入的Hyyp请求,当找到匹配的路由时,Kong会代理该请求,每个路由都关联到一个服务,Kong会运行用户配置在路由和其关联的服务中的插件,然后代理到上游服务
用户可以通过 Admin API 管理路由,路由有 hostspathsmethods 这些属性
如果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"
}

回顾:如何配置服务

通过调用Admin API请求,向Kong添加服务:

curl -i -X POST http://localhost:8001/services/ -d 'namefoo-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是一个简写参数,用于一次性填充 protocolhostspathsmethods 这些属性
现在我们指定一个路由,作为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
}

这样我们就配置好了路由,通过指定的 hostspaths 转发到 foo-service 服务
Kong是一个透明的代理,默认情况下它会将请求中的各种Header透传给上游服务

路由和匹配功能

现在我们讨论Kong是如何根据路由中配置的 hostspathsmethods 等属性匹配请求的,这三个属性都是可选的,但是必须指定其中一个,匹配路由的请求需要满足:

{
    "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

请求中的 Host 头

基于 Host 头转发请求非常普遍,Kong配置路由的 hosts 属性非常简便,hosts 可传入多个值,使用,分隔,添加多个 hosts 时使用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
...

现在也支持使用 [ ] 这种方式设置 hosts

curl -i -X POST http://localhost:8001/routes/ -d 'hosts[]=example.com' -d 'hosts[]=foo-service.com'
HTTP/1.1 201 Created
...

使用通配符 hostname

Kong允许用户在 hosts 属性中使用通配符来提高灵活性,但是不允许在最左侧或最右侧都使用通配符,通配符 hosts 示例:

{
    "hosts": ["*.example.com", "service.com"]
}

下面这些请求都可以匹配:

GET  /  HTTP/1.1  
Host:  an.example.com
GET  /  HTTP/1.1  
Host:  service.com

preserve_host 属性

Kong在代理过程中,默认会将上游服务中的 Host 头设置为服务中的 host,可以增加 preserve_host 属性来调整Kong的策略
下面这个示例中,路由中没有设置 preserve_host 属性:

{
    "hosts": ["service.com"],
    "service": {
        "id": "..."
    }
}

客户端发送请求:

GET / HTTP/1.1
Host: service.com

Kong会提取服务层的 hosts 属性塞入 Host 头,发送给 upstream:

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会保留客户端带入的 Host 头,发送给 upstream:

GET  /  HTTP/1.1  
Host:  service.com

请求路径

还有一种方式是通过请求路径匹配路由,匹配条件是客户端的请求路径的前缀必须满足 paths 属性值中的一个
示例路由如下:

{
    "paths": ["/service", "/hello/world"]
}

下面这些请求都可以匹配:

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在转发给 upstream stream 的时候不会更改URL路径,当路由匹配路径时,最长的路径前缀会优先匹配,比如用户可以在路由中定义两个路径:/service/service/resource,并确保前者不会掩盖后者

在 paths 中使用正则表达式

Kong支持路径使用Perl形式的正则表达式,路径中可以同时包含正则表达式和前缀形式,示例路由如下:

{
    "paths": ["/users/\d+/profile", "/following"]
}

下面这些请求都可以匹配:

GET  /following  HTTP/1.1  
Host:  ...
GET  /users/123/profile  HTTP/1.1  
Host:  ...

评估顺序

如上所述,Kong会根据路径的长度做评估,最长前缀路径优先匹配,同时Kong会根据 regex_priority 属性从高到低评估正则表达式的优先级,如下所示这些路由:

[
    {
        "paths": ["/status/\d+"],
        "regex_priority": 0
    },
    {
        "paths": ["/version/\d+/status/\d+"],
        "regex_priority": 6
    },
    {
        "paths": ["/version"],
    },
    {
        "paths": ["/version/any/"],
    }
]

这种情况下,Kong会按以下顺序评估传入的URI:

  1. /version/any/
  2. /version
  3. /version/\d+/status/\d+
  4. /status/\d+
    前缀路径总是比正则表达式优先评估,并且一个请求还需要同时匹配 hostsmethods,Kong会遍历用户配置的路径,找到匹配项最多的一个
捕获组

Kong的正则表达式还支持捕获组,这些从路径中提取的内容还可以在其他插件中使用,例如下面这个路由和请求路径:

/version/(?<version>\d+)/users/(?<user>\S+)
/version/1/users/john

如果请求与路由匹配,捕获组可以在插件中使用 ngx.ctx 变量获取:

local router_matches = ngx.ctx.router_matches
-- router_matches.uri_captures is:
-- { "1", "john", version = "1", user = "john" }
转义字符
curl -i -X POST http://localhost:8001/routes --data-urlencode 'uris[]=/status/\d+'
HTTP/1.1 201 Created
...

注意,curl 指令不会对自动对URL进行编码,并注意使用 --data-urlencode,防止Kong的Admin API对 + 字符进行URL解码

strip_path 属性

有可能客户端的请求路径匹配了某条路由,但是在 upstream 中不包含这个路径,我们可以使用 strip_path 属性配置路由:

{
    "paths": ["/service"],
    "strip_path": true,
    "service": {
        "id": "..."
    }
}

启用该标志位后,Kong在匹配路由会继续代理服务,然后在调用上游服务时在URL中去除匹配的部分,示例如下:

GET  /service/path/to/resource  HTTP/1.1  
Host:  ...
GET  /path/to/resource  HTTP/1.1  
Host:  ...

同样使用正则表达式的路由也同样支持 strip_path 属性:

{
    "paths": ["/version/\d+/service"],
    "strip_path": true,
    "service": {
        "id": "..."
    }
}

路由和上游服务示例如下:

GET  /version/1/service/path/to/resource  HTTP/1.1  
Host:  ...
GET  /path/to/resource  HTTP/1.1  
Host:  ...

请求方法

methods 属性限定了请求的 Http 方法,默认值为空,示例路由可以匹配 GETHEAD 方法:

{
    "methods": ["GET", "HEAD"],
    "service": {
        "id": "..."
    }
}

下面这些请求都可以匹配:

GET  /  HTTP/1.1  
Host:  ...
HEAD  /resource  HTTP/1.1  
Host:  ...

POSTDELETE 请求不会匹配路由,在路由上配置插件,可以更加细化服务的粒度,用户可以对同一个服务配置两条不同的路由,一个接收没有任何限制的 GET 请求,另外一个是 POST 请求,需要身份校验并且增加限流

匹配优先级

路由基于 hostspathsmethods 匹配请求,通常情况下Kong需要所有这些规则完全匹配,但是现实情况中可能会同时匹配多种路由,Kong定义了一个优先级规则来处理这种情况,这个规则就是,当评估一个请求时,Kong会选取匹配规则最多的路由,下面是两个路由示例:

{
    "hosts": ["example.com"],
    "service": {
        "id": "..."
    }
},
{
    "hosts": ["example.com"],
    "methods": ["POST"],
    "service": {
        "id": "..."
    }
}

第二条路由包含 hostsmethods 属性,所以他将优先被评估

GET  /  HTTP/1.1  
Host:  example.com

这会匹配第一个路由

POST  /  HTTP/1.1  
Host:  example.com

这会匹配第二个路由

代理行为

上面的代理规则解释了Kong如何将外部请求转发到用户的内部服务,下面我们将详细描述Kong匹配了路由之后,如果转发到内部服务的细节

1. 负载均衡

Kong实现了对内部服务池的负载均衡,具体信息可以参考负载均衡向导

2. 插件执行

Kong可以通过插件进行扩展,插件是一个钩子,生命周期从请求到响应,插件可以对用户环境做各种操作,也可以对代理的请求做各种转换
插件可以全局配置,也可以设置在特定的路由和服务上,所有这些都必须通过Admin API设置,当某个路由匹配时,Kong会运行关联在上面的插件,路由上的插件比服务上的插件优先运行,这些插件会运行 access 段中的内容,具体信息可以参考插件开发向导

3. 代理 & upstream 超时

当Kong执行完所有插件中包含的逻辑后,就准备把请求转发到 upstream 服务,这是通过Nginx的 ngx_http_proxy_module 模块完成的,用户可以配置下列服务属性,调整Kong和 upstream 服务之间的超时时间:

4. 错误 & 重试

每当在代理发生错误时,Kong会使用底层的Nginx重试机制将请求发送到下一个 upstream,这里有两个可配置的元素:

  1. 重试次数:可以使用 retries 属性配置服务的重试次数,具体信息可以参考Admin API
  2. 错误详情:Kong使用Nginx的默认值
    第二个选项基于Nginx的 proxy_next_upstream 指令,该选项不能通过Kong直接配置,需要自定义Nginx配置添加,详细信息可以参考配置向导

5. 响应

Kong接收来自 upstream service 的响应,以流的方式将其发送给下游客户端,此时添加在路由或者服务上的插件会依次执行 header_filter 段中的内容,当 header_filter 段中的内容全部执行完之后,以下这些头部文件会添加到返回给客户端的响应中:

配置 fallback 路由

Kong在实际使用中可以配置 fallback 路由增加路由的灵活性,这样可以避免返回 404 响应(找不到路由),用户可以捕获这些请求并将它们代理到一个特定的服务,或者对其使用一个特别的插件(比如可以使用一个不同的状态码返回或者中断代理该请求)
这是一个fallback路由的示例:

{
    "paths": ["/"],
    "service": {
        "id": "..."
    }
}

可以预见,所有的 Http 请求都会匹配此路由,因为所有的请求都是 / 前缀,在上几节我们已经知道了最长的URL会优先评估,所以 / 路径会被最后评估,这作为 fallback 路由相当有效,仅作为最后的处理手段,用户可以将它们路由到特殊的服务或者对其使用特别的插件

对路由配置 SSL 协议

Kong提供了对每个连接动态配置SSL协议的方法,SSL协议通过Admin API配置,由核心层直接处理,通过TLS协议连接Kong的客户端必须支持 SNI 扩展才能使用此功能,Kong Admin API通过下面两个参数处理SSL协议:

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 参数是一个语法糖,插入了SNI,并关联了上传的的证书,在例子中的 SNI 数组中有一个 SNI 包含了通配符,SNI 不允许在最左侧或最右侧都使用通配符,这在维护多个子域名时非常实用,有效的通配符位置包括 mydomain.**.mydomain.com*.www.mydomain.com
snis 匹配的优先级顺序是:

  1. 无通配符
  2. 前缀
  3. 后缀
    用户必须事先注册好这条路由,为了方便起见,我们仅使用 Host 做匹配条件:
curl -i -X POST http://localhost:8001/routes -d 'hosts=prefix.ssl-example.com,other-ssl-example.com' -d 'service.id=d54da06c-d69f-4910-8896-915c63c270cd'
HTTP/1.1 201 Created
...

现在这个路由可以代理Https请求

curl -i https://localhost:8443/ -H "Host: prefix.ssl-example.com"
HTTP/1.1 200 OK
...

现在如果客户发送的请求中将 prefix.ssl-example.com</font 作为 SNI 扩展,Kong在建立连接和 SSL 握手前,会提前配置好cert.pem` 证书

限定客户端协议(HTTP/HTTPS/TCP/TLS)

路由可以配置 protocols 属性限定监听的客户端协议,这个参数接受一系列参数:httphttpstcptls,路由同时支持 httphttps 协议可以这样配置:

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["http", "https"],
    "service": {
        "id": "..."
    }
}

protocols属性的默认值为 ["http", "https"]
仅使用Https协议的路由仅接收Https的流量

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["https"],
    "service": {
        "id": "..."
    }
}

如果有路由匹配了这条请求,但是没有使用SSL协议,Kong会返回:

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"}

从Kong1.0开始,可以使用原始TCP协议建立连接:

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["tcp"],
    "service": {
        "id": "..."
    }
}

同样,也可以使用原始TLS协议:

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["tls"],
    "service": {
        "id": "..."
    }
}

代理 WebSocket 流量

基于底层 Nginx 实现,Kong支持代理 WebSocket 流量,如果用户希望在客户端和 upstream service 之间建立连接,必须创建一个 WebSocket 握手,这是通过 Http 升级机制完成的,Kong会这样返回客户端:

GET / HTTP/1.1
Connection: Upgrade
Host: my-websocket-api.com
Upgrade: WebSocket

这样Kong会将 ConnectionUpgrade 转发到 upstream service,而不会像标准的 Http 代理那样解除连接

WebSocket 和 TLS

Kong会在 httphttps 协议对应的端口接收 wswss 连接,如果需要客户端强制使用TLS连接,需要将路由设置成仅支持https协议,当将服务指向 WebSocket 服务时,用户必须小心选择Kong与 upstream service 之间的协议,如果用户选择TLS(wss),那么上游的WebSocket服务必须定义成使用 https 协议,通常使用443端口;选择不使用TLS(ws),那么使用 http 协议,通常使用80端口

总结

通过本章指南,希望用户可以了解到Kong的代理机制

上一篇下一篇

猜你喜欢

热点阅读