Kong API网关使用笔记

背景

Kong是基于OpenResty的开源网关,其将API相关信息配置到postgresql或者是cassandra。通过lua扩展模块,扩展网关平台的功能。

本文记录着笔者在对Kong网关的具体使用过程。包括Kong部署,Kong组件使用,Kong插件使用。其他Kong相关的内容,可以到官网上查阅。

关注微信公众号:战渣渣
回复关键字“kong”获取测试代码

Kong部署

Kong的部署方式有很多种,这里采取的是基于Docker的部署过程。

postgresql部署

Docker部署Postgresql9.6

  • 创建网络
docker network create kong-net
  • 启动数据库
docker run -d --name kong-database \
--network=kong-net \
-p 5432:5432 \
-e "POSTGRES_USER=mykong" \
-e "POSTGRES_DB=mykong" \
-e "POSTGRES_PASSWORD=mykong" \
postgres:9.6

数据库高可用性,可将数据库做双机热备。笔者没有实际需求,下面是收集的一些资料并未执行,待到以后有需求再做执行

流复制+双机热备方案

  • 配置postgresql的主从流复制
    https://www.cnblogs.com/cxy486/p/5164612.html
  • 利用pgpool-II中间件实现高可用性
    https://www.iteye.com/blog/iwin-2108807
    https://www.xiaomastack.com/2019/08/16/postgresql%E9%9B%86%E7%BE%A4/
  • 利用keepalived+pgpoll-II完成双机热备切换
    https://www.cnblogs.com/songyuejie/p/4561089.html

部署kong

创建kong所需要数据库
创建数据库时指定的用户名,密码,数据库等,需要使用KONG_前缀的参数使用。
其他环境变量说明

docker run --rm \
--network=kong-net \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_USER=mykong" \
-e "KONG_PG_PASSWORD=mykong" \
-e "KONG_PG_DATABASE=mykong" \
kong:2.0.1 kong migrations bootstrap

使用kong最新版本 2.0.1

节点1:172.17.23.14

docker run -d --name kong  \
--network=kong-net \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_USER=mykong" \
-e "KONG_PG_PASSWORD=mykong" \
-e "KONG_PG_DATABASE=mykong" \
-e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \
-e "KONG_ADMIN_LISTEN_SSL=0.0.0.0:8444" \
-e "KONG_PROXY_ACCESS_LOG=/usr/local/kong/logs/proxy_access.log"     \
-e "KONG_ADMIN_ACCESS_LOG=/usr/local/kong/logs/admin_access.log"     \
-e "KONG_PROXY_ERROR_LOG=/usr/local/kong/logs/proxy_error.log"     \
-e "KONG_ADMIN_ERROR_LOG=/usr/local/kong/logs/admin_error.log" \
-e "KONG_TRUSTED_IPS=0.0.0.0/0,::/0"     \ # 使用IP-restriction插件时,保证真实IP地址的传递
-e "KONG_REAL_IP_HEADER=X-Forwarded-For" \ # 使用IP-restriction插件时,保证真实IP地址的传递
-p 8000:8000 \
-p 8443:8443 \
-p 8001:8001 \
-p 8444:8444 \
kong:2.0.1

节点2:172.17.23.17

docker run -d --name kong  \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=172.17.23.14" \
-e "KONG_PG_USER=mykong" \
-e "KONG_PG_PASSWORD=mykong" \
-e "KONG_PG_DATABASE=mykong" \
-e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \
-e "KONG_ADMIN_LISTEN_SSL=0.0.0.0:8444" \
-e "KONG_PROXY_ACCESS_LOG=/usr/local/kong/logs/proxy_access.log"     \
-e "KONG_ADMIN_ACCESS_LOG=/usr/local/kong/logs/admin_access.log"     \
-e "KONG_PROXY_ERROR_LOG=/usr/local/kong/logs/proxy_error.log"     \
-e "KONG_ADMIN_ERROR_LOG=/usr/local/kong/logs/admin_error.log" \
-e "KONG_TRUSTED_IPS=0.0.0.0/0,::/0"     \ # 使用IP-restriction插件时,保证真实IP地址的传递
-e "KONG_REAL_IP_HEADER=X-Forwarded-For" \ # 使用IP-restriction插件时,保证真实IP地址的传递
-p 8000:8000 \
-p 8443:8443 \
-p 8001:8001 \
-p 8444:8444 \
kong:2.0.1

配置NGINX

Kong部署了集群后,使用NGINX做负载均衡,为提高可用性,防止进群中某台服务宕机,NGINX对服务启用健康检查。

NGINX本身就具有被动检查,如下配置:

upstream kong_cluster{
    server 172.17.23.14:80  max_fails=1 fail_timeout=10s;
}

server {
    listen 80;
    server_name example.com; 
    location / {
      proxy_pass         http://kong_cluster;
    }
}

但是我这里使用的主动健康检查。

主动健康检查需要安装三方插件,这里使用的是淘宝开发的第三方模块,upstream_check模块,需要重新编译NGINX。

我的操作系统中本身已经有了nginx1.10。所以我要重新编译一下,将三方模块编译进去
参考资料

  • 下载主动检查模块
    下载地址
# git clone http://github.com/yaoweibin/nginx_upstream_check_module
  • 重新下载NGINX1.16.1
# 下载NGINX  1.16.1
wget http://nginx.org/download/nginx-1.16.1.tar.gz
## 因为我的操作系统中本身已经安装了nginx,所以我要重新安装,查看原来安装的命令,并把扩展的check模块编译到nginx中
# 查看原安装的命令
nginx -V 
# ubuntu系统下,安装nginx依赖包
# zlib依赖包
sudo apt-get install zlib1g-dev
# openssl依赖包
sudo apt-get install openssl libssl-dev
# 安装pcre依赖包
sudo apt-get install libpcre3 libpcre3-dev
# 安装 libxml2/libxslt
sudo apt-get install libxslt1-dev
sudo apt-get install libxml2-dev
# 安装GD依赖包
sudo apt-get install libgd-dev
# 安装 GeoIP依赖包
sudo apt-get install libgeoip-dev
# 重新安装下载的nginx1.16.1
tar zxvf nginx-1.16.1.tar.gz
cd nginx-1.16.1
# 下载三方模块并打补丁  根据nginx版本选定补丁
patch -p1 < /root/install/nginx_upstream_check_module/check_1.16.1+.patch
# 补丁打完之后,安装nginx
./configure \
--with-cc-opt='-g -O2 -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2' \
--with-ld-opt='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now' \
--prefix=/usr/share/nginx \
--conf-path=/etc/nginx/nginx.conf \
--http-log-path=/var/log/nginx/access.log \
--error-log-path=/var/log/nginx/error.log \
--lock-path=/var/lock/nginx.lock \
--pid-path=/run/nginx.pid \
--http-client-body-temp-path=/var/lib/nginx/body \
--http-fastcgi-temp-path=/var/lib/nginx/fastcgi \
--http-proxy-temp-path=/var/lib/nginx/proxy \
--http-scgi-temp-path=/var/lib/nginx/scgi \
--http-uwsgi-temp-path=/var/lib/nginx/uwsgi \
--with-debug \
--with-pcre-jit \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-http_realip_module \
--with-http_auth_request_module \
--with-http_addition_module \
--with-http_dav_module \
--with-http_geoip_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_image_filter_module \
--with-http_v2_module \
--with-http_sub_module \
--with-http_xslt_module \
--with-stream \
--with-stream_ssl_module \
--with-mail \
--with-mail_ssl_module \
--with-threads \
--with-pcre \
--add-module=/root/install/nginx_upstream_check_module
# 编译安装
make
# 如果是已经安装过了,只需要make
# 然后将生成nginx拷贝到原来的目录
cp objs/nginx /usr/sbin/nginx

部署nginx

# 配置https
upstream kong_cluster{
    # 最简单的轮询方式
    server 172.17.23.17:8000;
    server 172.17.23.14:8000;
    
    # 配置三方模块主动检查健康状态,检查TCP,只要确保Kong服务没有断掉即可,实际的服务健康检查使用Kong插件检查
    # 其他配置参考 http://tengine.taobao.org/document_cn/http_upstream_check_cn.html
    check interval=3000 rise=2 fall=3 timeout=1000 type=tcp;
}

server {
    listen 443 ssl;
    server_name example.com; # 更换自己的域名信息

    ssl_certificate   cert/214533299050973.pem;  # 更换成自己的证书信息
    ssl_certificate_key  cert/214533299050973.key; # 更换成自己的证书信息
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;

    # https 因为安全原因,需要禁用 gzip
    # 但是在一些场景下,不需要禁用
    # gzip off;
    gzip on;
    # 设置允许压缩的页面最小字节数
    gzip_min_length 1000;
    gzip_buffers 4 16k;
    gzip_http_version 1.1;
    # 1~9,默认为1,数值越大,压缩率越高,CPU占用越多,时间越久
    gzip_comp_level 3;
    gzip_vary on;
    # 禁用对 IE 6 使用 gzip 压缩
    gzip_disable "MSIE [1-6]\.";
    gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/javascript "application/javascript; charset=utf-8" application/xml application/xml+rss application/json "application/json; charset=utf-8" font/ttf font/otf image/svg+xml;

    # 设置最大允许的POST数据量,如果提交的文件超过这个值,会出现413错误
    client_max_body_size 20m;
    keepalive_timeout 15;

    # 不显示 nginx 的版本号
    server_tokens off;

    # 设置请求头的长度大小
    client_header_buffer_size 32k;
    large_client_header_buffers 4 32k;


    ## Individual nginx logs
    access_log  /var/log/nginx/kong_access.log;
    error_log   /var/log/nginx/kong_error.log;

    location ^~ /robots.txt {
        expires 30d;
        # add_header Content-Type text/plain;
        return 200 "User-Agent:  *\nDisallow:  /";
    }

    location ~ /(\w+) {
        # websocket 支持
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 真实地址传递,IP限制插件使用
        proxy_pass http://kong_cluster;
        expires -1;
    }

    # 主页返回 404
    location = / {
        return 404;
    }
}

server {
   listen 80;
   server_name  example.com; # 更换自己的域名信息
   #告诉浏览器有效期内只准用 https 访问
   add_header Strict-Transport-Security max-age=15768000;
   #永久重定向到 https 站点
   return 301 https://$server_name$request_uri;
}

重启NGINX

systemctl restart nginx

将API配置到Kong

Kong常用组件说明

  • Service
    Service是定义的服务,通过Kong转发后根据请求的协议,host,method,path匹配到实际的服务地址。
    
    Service可以与Route进行关联,一个Service可以有很多Route, Route匹配后就会转发到Service,
    处理过程中也会通过Plugin的处理,增加或者减少一些相应的Header或者其他信息
    
    Service可以是一个实际的地址,也可以是Kong内部提供的Upstream组件关联,由Upstream将请求转发到实际的服务。
  • Route:
    Route就是路由,实际就是我们通过定义一些规则来匹配客户端的请求,每个路由都会关联一个Service,
    并且Service可以关联多个Route,当匹配到客户端的请求时,每个请求都会被代理到其配置的Service中
    
    Route作为客户端的入口,通过将Route和Service的松耦合,可以通过hosts path等规则的配置,最终让请求转发到不同的Service。
    
    例如,我们规定api.example.com 和 api.service.com的登录请求都能够代理到123.11.11.11:8000端口上,那我们可以通过hosts和path来路由
    
    首先,创建一个Service s1,其相应的host和port以及协议为http://123.11.11.11:8000
    然后,创建一个Route,关联的Service为s1,其hosts为[api.service.com, api.example.com],path为login
    最后,将域名api.example.com和api.service.com的请求转到到我们的Kong集群上,也就是我们上面一节中通过Nginx配置的请求地址
    那么,当我们请求api.example.com/login和api.service.com/login时,其通过Route匹配,然后转发到Service,最终将会请求我们自己的服务。
 
  • Upstream
    这是指您自己的API /服务位于Kong后面,客户端请求被转发到该服务器。
    
    相当于Kong提供了一个负载的功能,基于Nginx的虚拟主机的方式做的负载功能
    
    当我们部署集群时,一个单独的地址不足以满足我们的时候,我们可以使用Kong的upstream来进行设置
    
    首先在service中指定host的时候,可以指定为我们的upstream定义的hostname
    
    我们在创建upstream时指定名字,然后指定solts(暂时不确定具体作用),upstream可以进行健康检查等系列操作。这里先不开启(还没有研究)
    
    然后我们可以再创建target类型,将target绑定到upstream上,那么基本上我们部署集群时,也可以使用
  • Target
    target 就是在upstream进行负载均衡的终端,当我们部署集群时,需要将每个节点作为一个target,并设置负载的权重,当然也可以通过upstream的设置对target进行健康检查。
    当我们使用upstream时,整个路线是 Route >> Service >> Upstream >> Target 
  • Consumer
    Consumer 可以代表一个服务,可以代表一个用户,也可以代表消费者,可以根据我们自己的需求来定义
    
    可以将一个Consumer对应到实际应用中的一个用户,也可以只是作为一个Service的请求消费者
    
    Consumer具体可以在Plugin使用时再做深入了解
  • Plugin
    在请求被代理到上游API之前或之后执行Kong内的动作的插件。
    
    例如,请求之前的Authentication或者是请求限流插件的使用
    
    Plugin可以和Service绑定,也可以和Route以及Consumer进行关联,也可以是全局的插件。

    常用的限流插件,IP限制插件等。
    

Kong使用

主要使用4个组件Service,Route,Upstream和Target

Target配置的就是服务节点,不过要将其关联到到Upstream。

Upstream可以对Target做负载均衡和健康检查等,通过设置name于Service关联,Service匹配到请求时转发到相应的Upstream。

Service根据业务设置不同的服务,Service和Route绑定获取实际请求,通过protocol,host,port和path匹配请求,并将匹配到的请求转发到Upstream。

Route是Kong中实际的入口点。主要根据protocols,hosts,methods,paths设置,匹配请求,将请求转发到相应的服务上

我们按照这个顺序,依次创建Service,Route,Upstream和Target

配置Service

Service还有一些其他参数,可根据具体需求定制。官网资料

  • 添加命令
curl -i -X POST \
--url http://localhost:8001/services/ \
--data 'name=example-service' \
--data 'protocol=http' \
--data 'host=api.example.service'
    
# host属性在创建Upstream时使用
  • 添加结果
{
    "host": "api.example.service",
    "created_at": 1582626079,
    "connect_timeout": 60000,
    "id": "71b28160-15fc-4215-a68f-d0b607e9cb9f",
    "protocol": "http",
    "name": "example-service",
    "read_timeout": 60000,
    "port": 80,
    "path": null,
    "updated_at": 1582626079,
    "retries": 5,
    "write_timeout": 60000,
    "tags": null,
    "client_certificate": null
}
  • 修改命令
curl -i -X PATCH \
--url http://localhost:8001/services/71b28160-15fc-4215-a68f-d0b607e9cb9f/ \
--data 'name=test-kong' \
--data 'protocol=http'  \
--data 'host=test.kong.service'
  • 修改结果
{
    "host": "test.kong.service",
    "created_at": 1582626079,
    "connect_timeout": 60000,
    "id": "71b28160-15fc-4215-a68f-d0b607e9cb9f",
    "protocol": "http",
    "name": "test-kong",
    "read_timeout": 60000,
    "port": 80,
    "path": null,
    "updated_at": 1582626120,
    "retries": 5,
    "write_timeout": 60000,
    "tags": null,
    "client_certificate": null
}

配置Route

Route还有一些其他参数,可根据具体需求定制。官网资料

  • 添加命令

实际使用时,将hosts变更为实际的域名

curl -i -X POST \
--url http://localhost:8001/routes/ \
--data 'protocols[]=http' \
--data 'protocols[]=https' \
--data 'methods[]=GET' \
--data 'methods[]=POST' \
--data 'methods[]=DELETE' \
--data 'methods[]=PUT' \
--data 'hosts[]=api.example.com' \
--data 'paths[]=/v1/test_kong' \
--data 'strip_path=false' \ # 此属性代表是否清除原有的path,默认是清除,如果不需要清除需要再添加的时候指定为false
--data 'service.id=71b28160-15fc-4215-a68f-d0b607e9cb9f'
  • 添加结果
{
    "id": "c156de0e-4b08-43a3-bc2d-3f0cf5d5d00a",
    "path_handling": "v0",
    "paths": ["\/v1\/test_kong"],
    "destinations": null,
    "headers": null,
    "protocols": ["http", "https"],
    "methods": ["GET", "POST", "DELETE", "PUT"],
    "snis": null,
    "service": {
        "id": "71b28160-15fc-4215-a68f-d0b607e9cb9f"
    },
    "name": null,
    "strip_path": false,
    "preserve_host": false,
    "regex_priority": 0,
    "updated_at": 1582627872,
    "sources": null,
    "hosts": ["api.example.com"],
    "https_redirect_status_code": 426,
    "tags": null,
    "created_at": 1582627872
}

配置Upstream

  • 添加命令

这里只配置主动检查

curl -i -X POST \
--url http://localhost:8001/upstreams/ \
--data 'name=test.kong.service'  \
--data 'algorithm=round-robin' \
--data 'healthchecks.active.type=http' \
--data 'healthchecks.active.http_path=/' \
--data 'healthchecks.active.timeout=2' \
--data 'healthchecks.active.healthy.successes=3' \
--data 'healthchecks.active.healthy.interval=10' \
--data 'healthchecks.active.unhealthy.interval=10' \
--data 'healthchecks.active.unhealthy.http_failures=3'
  • 添加结果
{
    "created_at": 1582626649,
    "hash_on": "none",
    "id": "1d3638c6-d5a0-4bb5-907b-015c8daf7861",
    "algorithm": "round-robin",
    "name": "test.kong.service",
    "tags": null,
    "hash_fallback_header": null,
    "hash_fallback": "none",
    "hash_on_cookie": null,
    "host_header": null,
    "hash_on_cookie_path": "\/",
    "healthchecks": {
        "threshold": 0,
        "active": {
            "unhealthy": {
                "http_statuses": [429, 404, 500, 501, 502, 503, 504, 505],
                "tcp_failures": 0,
                "timeouts": 0,
                "http_failures": 3,
                "interval": 10
            },
            "type": "http",
            "http_path": "\/",
            "timeout": 2,
            "healthy": {
                "successes": 3,
                "interval": 10,
                "http_statuses": [200, 302]
            },
            "https_sni": null,
            "https_verify_certificate": true,
            "concurrency": 10
        },
        "passive": {
            "unhealthy": {
                "http_failures": 0,
                "http_statuses": [429, 500, 503],
                "tcp_failures": 0,
                "timeouts": 0
            },
            "healthy": {
                "http_statuses": [200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308],
                "successes": 0
            },
            "type": "http"
        }
    },
    "hash_on_header": null,
    "slots": 10000
}

配置target

  • 添加命令
curl -i -X POST \
--url http://localhost:8001/upstreams/1d3638c6-d5a0-4bb5-907b-015c8daf7861/targets \
--data 'target=172.17.23.14:38001'
    
curl -i -X POST \
    --url http://localhost:8001/upstreams/1d3638c6-d5a0-4bb5-907b-015c8daf7861/targets \
    --data 'target=172.17.23.14:38002'

  • 添加结果
{
    "created_at": 1582627463.954,
    "upstream": {
        "id": "1d3638c6-d5a0-4bb5-907b-015c8daf7861"
    },
    "id": "66c83e7d-f006-43da-990b-d493a966fc66",
    "target": "172.17.23.14:38001",
    "weight": 100
}

{
    "created_at": 1582627517.872,
    "upstream": {
        "id": "1d3638c6-d5a0-4bb5-907b-015c8daf7861"
    },
    "id": "d9940f44-30ad-437a-aaab-836dfaf27b93",
    "target": "172.17.23.14:38002",
    "weight": 100
}
检查配置kong是否可行
  • 检查配置的请求
curl -X GET https://example.com/v1/test_kong

{"code": 200, "message": "test kong get success"}

curl -X POST https://example.com/v1/test_kong

{"code": 200, "message": "test post get success"}

curl -X DELETE https://example.com/v1/test_kong

{"code": 200, "message": "test kong delete success"}

curl -X PUT https://example.com/v1/test_kong

{"code": 200, "message": "test kong put success"}

  • 检查配置的健康检查

看到服务配置的东西一直在被检查

[I 200225 19:03:10 web:2246] 200 GET / (172.17.23.14) 0.88ms
[I 200225 19:03:20 web:2246] 200 GET / (172.17.23.17) 1.34ms
[I 200225 19:03:20 web:2246] 200 GET / (172.17.23.14) 0.67ms
[I 200225 19:03:30 web:2246] 200 GET / (172.17.23.14) 0.88ms
[I 200225 19:03:30 web:2246] 200 GET / (172.17.23.17) 1.02ms
[I 200225 19:03:40 web:2246] 200 GET / (172.17.23.14) 0.88ms
[I 200225 19:03:40 web:2246] 200 GET / (172.17.23.17) 1.15ms

Kong插件使用

插件的作用范围,从小到大可以是 Route,Service,Consumer,Global。

可以根据需求使用一些插件,比如做一些限流操作,IP白名单和IP黑名单等

下面是Kong官方就提供的开源版可用的插件

Rate Limiting 限流插件

限流插件,限制在指定时间内可以访问的次数,可以是几秒内,几分钟内,几小时,几天等。
插件可以设置在Service,Route或者全局,如果启用Consumer,则根据Consumer来计算限制时间,否则根据客户端IP地址

给定service添加插件

curl -X POST http://localhost:8001/services/71b28160-15fc-4215-a68f-d0b607e9cb9f/plugins \
    --data "name=rate-limiting"  \
    --data "config.second=2" \
    --data "config.hour=10000" \
    --data "config.policy=local" \
    --data "config.limit_by=ip"

添加结果

{
    "created_at": 1582637075,
    "config": {
        "minute": null,
        "policy": "local",
        "month": null,
        "redis_timeout": 2000,
        "limit_by": "consumer",
        "hide_client_headers": false,
        "second": 5,
        "day": null,
        "redis_password": null,
        "year": null,
        "redis_database": 0,
        "hour": 10000,
        "redis_port": 6379,
        "redis_host": null,
        "fault_tolerant": true
    },
    "id": "9f8d08c9-4bdf-4912-b4dd-f96b32e38019",
    "service": {
        "id": "71b28160-15fc-4215-a68f-d0b607e9cb9f"
    },
    "enabled": true,
    "protocols": ["grpc", "grpcs", "http", "https"],
    "name": "rate-limiting",
    "consumer": null,
    "route": null,
    "tags": null
}

测试效果

def test_rate_limiting():
    url = "https://example.com/v1/test_kong"
    def _request(idx):
        print("request {} start by : {}".format(idx, datetime.datetime.now()))
        print("request {} result >>>".format(idx))
        resp = requests.get(url)
        print("request {} status_code {} ".format(idx, resp.status_code))
        print("request {} json {} ".format(idx, resp.json()))
        print("request {} result <<<".format(idx))
        print("request {} end by : {}".format(idx, datetime.datetime.now()))

    for i in range(0, 8):
        t = threading.Thread(target=_request, args=(i, ))
        t.start()

执行结果

request 3 status_code 200
request 3 json {'message': 'test kong get success', 'code': 200}
request 3 result <<<
request 3 end by : 2020-02-25 21:45:18.566325
request 6 status_code 429
request 6 json {'message': 'API rate limit exceeded'}
request 5 status_code 429
IP Restriction

IP Restriction就是通过设置IP白名单和黑名单,根据客户端IP来对一些请求进行拦截和防护,通过插件的源码可以看到其工作原理是,通过NGINX的变量binary_remote_addr以及设置的黑名单和白名单来进行限制,先看黑名单再看白名单,就是说白名单优先级更高一些,如果同时设置一个IP即在黑名单又在白名单列表,那么这个IP是可以进行访问。

IP Restriction插件可以作用于Service, Route, Consumer, 也可以作用于全局。
我们现在将其作用于Service上

curl -X POST http://localhost:8001/services/71b28160-15fc-4215-a68f-d0b607e9cb9f/plugins \
--data "name=ip-restriction" \
--data "config.whitelist=172.17.23.14" \
--data "config.whitelist=172.17.23.17"

添加结果

{
    "created_at": 1582980903,
    "config": {
        "whitelist": ["172.17.23.14", "172.17.23.17"],
        "blacklist": null
    },
    "id": "c0364fe4-2d43-451e-9330-b45964cf1483",
    "service": {
        "id": "71b28160-15fc-4215-a68f-d0b607e9cb9f"
    },
    "enabled": true,
    "protocols": ["grpc", "grpcs", "http", "https"],
    "name": "ip-restriction",
    "consumer": null,
    "route": null,
    "tags": null
}

测试结果

{"message":"Your IP address is not allowed"}
Zipkin

Zipkin是分布式跟踪系统,我们使用者插件的目的是来监控我们的API耗时情况,尤其是可以根据这些结果来跟踪相应的性能消耗。

所以我们先部署Zipkin服务,直接使用Docker安装。

zipkin具体信息,查看官网

部署Zipkin
docker run -d -p 9411:9411 openzipkin/zipkin
添加插件

zipkin插件可以作用于Service, Route, Consumer, 也可以作用于全局。
我们现在将其作用于Service上

curl -X POST http://localhost:8001/services/71b28160-15fc-4215-a68f-d0b607e9cb9f/plugins \
    --data "name=zipkin"  \
    --data "config.http_endpoint=http://172.17.23.14:9411/api/v2/spans" \
    --data "config.sample_ratio=1" \   # 全部采样
    --data "config.include_credential=true"

添加结果

{
    "created_at": 1582812902,
    "config": {
        "sample_ratio": 1,
        "http_endpoint": "http:\/\/172.17.23.14:9411\/api\/v2\/spans",
        "include_credential": true,
        "default_service_name": null
    },
    "id": "c58654d8-2fd5-43b6-854f-0fd65a353f7e",
    "service": {
        "id": "71b28160-15fc-4215-a68f-d0b607e9cb9f"
    },
    "enabled": true,
    "protocols": ["grpc", "grpcs", "http", "https"],
    "name": "zipkin",
    "consumer": null,
    "route": null,
    "tags": null
}

http_endpoint:zipkin的服务地址

sample_ratio:采集的频率 设置为1时全部采集,设置为0时,全部不采集, 设置为0-1之间的小数,则会采
用算法按照设定的参数随机采集

测试插件使用效果:
访问配置的地址之后,前面设置的采集频率是1,表示全部采集,访问之后登陆zipkin的地址http://172.17.23.14:9411/zipkin

在这里插入图片描述

HMAC Authentication

HMAC签名身份验证,Kong网关签名验证通过后,才可以向后请求,实际应用场景听多,具体流程如下:

  1. 创建消费者Consumer
  2. 给消费Consumer创建插件HMAC对应的username和secret,客户端使用其签名
  3. 在Service或者Route中启用HMAC插件
  4. 客户端在请求时,根据前面返回的username和secret签名,Kong网关匹配后进行签名验证
  • 创建消费者

创建命令

curl -i http://localhost:8001/consumers/ -d "username=test_hmac&custom_id=test_hmac_id"

创建结果

{
    "custom_id": "test_hmac_id",
    "created_at": 1583071044,
    "id": "c9a4ad0e-174f-4a7f-81b0-ce9e3261c177",
    "tags": null,
    "username": "test_hmac"
}
  • 创建消费者相应HMAC插件信息
curl -X POST http://localhost:8001/consumers/test_hmac/hmac-auth \
--data "username=test_hmac" \
--data "secret=test_hmac_secret123"

username:在HMAC签名验证中使用的用户名。
secret: 在HMAC签名验证中使用的秘钥。

创建结果

{
    "created_at": 1583071212,
    "consumer": {
        "id": "c9a4ad0e-174f-4a7f-81b0-ce9e3261c177"
    },
    "id": "834a8f69-c63d-4183-8a50-b52ab6a6187e",
    "tags": null,
    "secret": "test_hmac_secret123",
    "username": "test_hmac"
}
  • 启用HMAC插件

先把之前的Service上的IP Restriction插件去掉。否则我本地和服务器上的测试会提示无法处理

根据之前创建插件产生的ID,直接删除即可。

curl -i -X DELETE http://localhost:8001/plugins/c0364fe4-2d43-451e-9330-b45964cf1483

把插件添加到指定的Route或者Service都可以,这次把这个插件加到Route上,也来测试一遍

添加命令

curl -i -X POST http://localhost:8001/routes/c156de0e-4b08-43a3-bc2d-3f0cf5d5d00a/plugins \
      -d "name=hmac-auth" \
      -d "config.enforce_headers=date" \
      -d "config.algorithms=hmac-sha1" \
      -d "config.algorithms=hmac-sha256" \
      -d "config.validate_request_body=true"
enforce_headers: Header中必须要有的参数,后续用于签名验证

algorithms:用户想要支持的HMAC摘要算法列表。允许的值是hmac-sha1,hmac-sha256,
hmac-sha384,和hmac-sha512客户端请求时,从中选择一个即可

validate_request_body:启用验证请求体,设置为true之后,客户端请求头中若是有Digest,
Kong则会使用HMAC算法验证请求体body。

添加结果

{
    "created_at": 1583072768,
    "config": {
        "clock_skew": 300,
        "validate_request_body": true,
        "enforce_headers": ["date"],
        "algorithms": ["hmac-sha1", "hmac-sha256"],
        "anonymous": null,
        "hide_credentials": false
    },
    "id": "39188d02-5425-4f76-8795-5e028f793476",
    "service": null,
    "enabled": true,
    "protocols": ["grpc", "grpcs", "http", "https"],
    "name": "hmac-auth",
    "consumer": null,
    "route": {
        "id": "c156de0e-4b08-43a3-bc2d-3f0cf5d5d00a"
    },
    "tags": null
}
  • 客户端请求测试
重要请求头信息
1. Authorization 或者 Proxy-Authorization
    这个头的组成是
    credentials 签名算法  使用HMAC插件,固定值就是hmac
    params 具体的参数,参数用","分割。
        algorithm: 签名算法 
        headers: 签名计算包括的key 
        signature: 生成的签名,生成规则 
2. Digest:启用validate_request_body,需要生成
3. Date:必须是GMT时间格式

Python测试函数

def test_hmac_authentication():
    # 创建消费者对应HMAC插件时的用户名和秘钥
    hmac_username = "test_hmac"
    hmac_secret = "test_hmac_secret123"

    url = "https://example.com/v1/test_kong"

    # 请求头
    # 注意,请求头的时间格式必须是GMT时间格式
    request_headers = {
        "Authorization": "",
        "Digest": "",
        "Date": datetime.datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT")
    }

    # 请求体
    request_body = {
        "data": {"test": "hmac"}
    }

    # 生成Digest
    #  body="A small body"
    #  digest=SHA-256(body)
    #  base64_digest=base64(digest)
    #  Digest: SHA-256=
    base64_digest = base64.b64encode(hashlib.sha256(json.dumps(request_body).encode('utf-8')).digest()).decode('utf-8')
    request_headers['Digest'] = "SHA-256={}".format(base64_digest)

    # 生成签名
    # signing_string = "date: Thu, 22 Jun 2017 17:15:21 GMT\nGET /requests HTTP/1.1"
    # digest = HMAC - SHA256( < signing_string >, "secret")
    # base64_digest = base64( < digest >)
    # 签名字符串构造
    # 如果参数不是request-line,则名字用小写,然后跟":"和一个空格" ",
    # 如果参数是request-line,则追加http请求行【GET /requests HTTP/1.1】
    # 中间用换行符"\n"将所有的参数连接起来。

    # 按照上述规则拼接字符串
    sign_string = "date: {}\ndigest: {}".format(request_headers['Date'], request_headers['Digest'])

    # 开始签名
    sign_digest = hmac.new(hmac_secret.encode("utf-8"), sign_string.encode('utf-8'), digestmod='sha256').digest()
    sign_base64_digest = base64.b64encode(sign_digest).decode('utf-8')

    # 构建Authorization参数 具体信息 https://docs.konghq.com/hub/kong-inc/hmac-auth/#signature-authentication-scheme
    authorization = 'hmac username="{}", algorithm="hmac-sha256", headers="date digest", signature="{}"'.format(
        hmac_username, sign_base64_digest)

    request_headers['Authorization'] = authorization
    resp = requests.post(url, headers=request_headers, json=request_body)
    print(request_headers)
    print(resp.status_code)
    print(resp.json())

测试结果

# 失败
401
{'message': 'HMAC signature cannot be verified'}

# 成功
200
{'code': 200, 'message': 'test kong post success'}

你可能感兴趣的:(Kong API网关使用笔记)