Kong代理向导

简介

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

  • proxy_listen:其中定义了一系列地址和端口,接收来自客户端的公共流量,代理到用户的上游服务,默认为 8000
  • admin_listen:同样定义了一些列地址和端口,但限制为仅供管理员访问,其中暴露了Kong的配置功能,即 Admin API,默认为 8001

术语

  • client:向Kong代理端口发出请求的应用
  • upstream service:位于Kong服务后台的用户自己的API服务,通过Kong转发客户端请求
  • service:服务实体,每个上游服务的抽象
  • route:Kong的路由实体,其中定义了请求路由到对应服务的规则
  • plugin:在代理生命周期中运行的业务逻辑,可以全局设置,也可以设置在特定的路由和服务上

概要

纵观全局,Kong服务监听其配置代理端口的Http流量(默认为 80008443 端口),然后根据用户配置的路由尝试匹配流入的Hyyp请求,当找到匹配的路由时,Kong会代理该请求,每个路由都关联到一个服务,Kong会运行用户配置在路由和其关联的服务中的插件,然后代理到上游服务
用户可以通过 Admin API 管理路由,路由有 hostspaths、methods 这些属性
如果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是一个简写参数,用于一次性填充 protocol、hosts、paths、methods 这些属性
现在我们指定一个路由,作为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是如何根据路由中配置的 hosts、pathsmethods 等属性匹配请求的,这三个属性都是可选的,但是必须指定其中一个,匹配路由的请求需要满足:

  • 请求必须包含路由中配置的所有属性;
  • 对于单个属性,至少匹配属性中的某一个值;
    示例路由:
{
    "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 变量获?。?/p>

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 请求,需要身份校验并且增加限流

匹配优先级

路由基于 hosts,pathsmethods 匹配请求,通常情况下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 服务之间的超时时间:

  • upstream_connect_timeout:定义与服务建立连接的超时时间,单位是毫秒,默认值为 60000
  • upstream_send_timeout:定义连续两次向 upstream 服务发送请求的超时时间,单位是毫秒,默认值为 60000
  • upstream_read_timeout:定义连续两次向 upstream 服务读取请求的超时时间,单位是毫秒,默认值为 60000
    Kong会使用HTTP/1.1协议发送请求,并设置下列头文件:
  • Host: <your_upstream_host>:主机名
  • Connection: keep-alive:允许重用 upstream 连接
  • X-Real-IP: <remote_addr>:remote_addr 与 ngx_http_core_module ??橹卸ㄒ宓耐淞恳恢?,但是这个值可能被 ngx_http_realip_module 覆盖
  • X-Forwarded-For: <address>:待验证
  • X-Forwarded-Proto:待验证
  • X-Forwarded-Host: <host>:待验证
  • X-Forwarded-Port: <port>:待验证
    Kong会转发所有其他请求头,使用 WebSocket 协议时会出现一个例外,Kong会设置下列头文件:
  • Connection: Upgrade
  • Upgrade: websocket

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 段中的内容全部执行完之后,以下这些头部文件会添加到返回给客户端的响应中:

  • Via: kong/x.x.x:x.x.x是Kong的版本信息
  • X-Kong-Proxy-Latency: <latency>:latency是Kong从客户端接收到请求到将请求转发到 upstream service 所经历的时间,单位为毫秒
  • X-Kong-Upstream-Latency: <latency>:latency是Kong从 upstream service 接收到响应所等待的时间,单位为毫秒
    当响应头发送给客户端后,Kong会执行插件 body_filter 段中的内容,这个钩子可能会执行多次,这依赖Nginx的流特性,具体信息可以参考插件开发向导

配置 fallback 路由

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

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

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

对路由配置 SSL 协议

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

  • /certificates:存储用户的密钥和证书
  • /snis:与注册在SNI上的证书关联
    你可以在 Admin API 向导中找到这两个参数的文档,这是如何配置SSL证书的示例,首先通过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 属性限定监听的客户端协议,这个参数接受一系列参数:http、https、tcptls,路由同时支持 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的代理机制

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容