Kong网关:入门、实战与进阶
上QQ阅读APP看书,第一时间看更新

4.5 项目实践

这里,笔者会带领读者使用OpenResty和PostgreSQL数据库实现黑白名单功能。项目目录结构如下。


.
├── docker-compose.yml
├── openresty
│   ├── Dockerfile
│   ├── init.sql
│   ├── lua
│   │   ├── blacklist.lua
│   │   └── whitelist.lua
│   └── nginx.conf
└── postgres
    └── Dockerfile

其中,docker-compose.yml文件内容如代码清单4-8所示。

程序清单4-8 docker-compose.yml文件


  1 version: "3"
  2 services:
  3   postgres:
  4     container_name: postgres
  5     build:
  6       context: ./postgres
  7     environment:
  8       POSTGRES_DB: openresty
  9       POSTGRES_USER: openresty
 10       POSTGRES_PASSWORD: openresty
 11     ports: ['5432:5432']
 12     networks:
 13       extnetwork:
 14         ipv4_address: 172.19.96.2
 15   openresty:
 16     container_name: openresty
 17     build:
 18       context: ./openresty
 19     depends_on:
 20       - postgres
 21     ports: ['80:80']
 22     volumes:
 23       - ./openresty/lua:/opt/openresty/nginx/lua
 24     networks:
 25       extnetwork:
 26         ipv4_address: 172.19.96.3
 27     command: /bin/bash -c "psql postgres://openresty:openresty@
      172.19.96.2:5432/openresty -f /init.sql && openresty -c 
      conf/nginx.conf && tail -f /etc/hosts"
 28 networks:
 29   extnetwork:
 30     ipam:
 31       config:
 32         - subnet: 172.19.96.0/16

该文件指定了OpenResty与PostgreSQL数据库的IP地址,以便于数据初始化。这两者都依赖Dockerfile文件构建镜像,其中postgres目录中仅包含Dockerfile文件,内容如下:


1 FROM postgres:9.6

openresty目录中包含Dockerfile、init.sql、nginx.conf和lua文件夹,其中Dockerfile文件内容如代码清单4-9所示。

程序清单4-9 Dockerfile文件


  1 FROM centos:7
  2 RUN yum install -y perl wget pcre-devel openssl-devel gcc curl 
      postgresql-devel make
  3 RUN wget https://openresty.org/download/openresty-1.17.8.1.tar.gz \
  4 && tar xf openresty-1.17.8.1.tar.gz -C /root
  5 RUN cd /root/openresty-1.17.8.1 \
  6 && ./configure \
  7   --prefix=/opt/openresty \
  8   --with-http_postgres_module \
  9   --with-luajit \
 10   --without-http_redis2_module \
 11   --with-http_iconv_module \
 12 && make \
 13 && gmake install
 14 RUN ln -s /opt/openresty/bin/openresty /usr/bin/openresty
 15 RUN mkdir /opt/openresty/nginx/lua
 16 RUN rm /opt/openresty/nginx/conf/nginx.conf
 17 ADD nginx.conf /opt/openresty/nginx/conf/
 18 ADD init.sql /
 19 CMD ["openresty","-c","conf/nginx.version1.conf","&"]

init.sql为数据库初始化文件。该文件包含一张表,表名为t_ip_restriction,表中包含的字段如下。


  1 CREATE TABLE "public"."t_ip_restriction" (
  2   "id" serial2,
  3   "type" varchar(255) NOT NULL,
  4   "address" text NOT NULL,
  5   "path" text NOT NULL,
  6   PRIMARY KEY ("id")
  7 )
  8 ;
  9 COMMENT ON COLUMN "public"."t_ip_restriction"."id" IS '自增id';
 10 COMMENT ON COLUMN "public"."t_ip_restriction"."type" IS '类型:区分黑白名单';
 11 COMMENT ON COLUMN "public"."t_ip_restriction"."address" IS 'IP地址';
 12 COMMENT ON COLUMN "public"."t_ip_restriction"."path" IS '路径,路由信息';

nginx.conf为该项目的配置文件,内容如代码清单4-10所示。

程序清单4-10 nginx.conf配置文件


  1 worker_processes  1;
  2 events {
  3   worker_connections  1024;
  4 }
  5
  6 http {
  7   include       mime.types;
  8   default_type  application/octet-stream;
  9   sendfile        on;
 10   keepalive_timeout  65;
 11   upstream database {
 12     postgres_server 172.19.96.2 dbname=openresty
 13             user=openresty password=openresty;
 14   }
 15   server {
 16     listen       80;
 17     server_name  localhost;
 18     location / {
 19       content_by_lua_block {
 20         ngx.say("openresty")
 21       }
 22     }
 23     location ~ /insert {
 24       rds_json            on;
 25       postgres_pass       database;
 26       postgres_query      "INSERT INTO t_ip_restriction 
            (address,type,path) VALUES('$arg_ip','$arg_type','$arg_path') 
            RETURNING *";
 27     }
 28     location /query {
 29       rds_json            on;
 30       postgres_pass       database;
 31       postgres_query      "SELECT distinct address FROM 
            t_ip_restriction WHERE type='$arg_type'";
 32     }
 33     location /blacklist/demo/test {
 34       proxy_pass http://www.baidu.com;
 35       content_by_lua_file lua/blacklist.lua;
 36     }
 37     location /whitelist/demo/test {
 38       proxy_pass http://www.baidu.com;
 39       content_by_lua_file lua/whitelist.lua;
 40     }
 41   }
 42 }

该文件配置了多个接口,对应不同的功能,具体如下。

·/接口:空接口,用于做性能基准测试。

·/insert接口:用于向数据库添加黑白名单配置,请求参数依次为IP地址、黑白名单类型和插件对应路径。

·/query接口:用于根据黑白名单类型查询对应列表IP地址。

·/blacklist/demo/test接口:代理接口示例,演示黑名单功能。

·/whitelist/demo/test接口:代理接口示例,演示白名单功能。

blacklist.lua和whitelist.lua文件中包含了黑白名单的校验逻辑,具体内容如代码清单4-11和代码清单4-12所示。

程序清单4-11 blacklist.lua文件


  1 local cjson = require "cjson"
  2 local res = ngx.location.capture('/query?type=black')
  3 local body = res.body
  4 local body_table = cjson.decode(body)
  5 local ip = {ngx.var.remote_addr}
  6 blacklist = {}
  7 for i in pairs(body_table) do
  8   iplist = body_table[i]
  9   local blackip = iplist["address"]
 10   table.insert(blacklist, blackip)
 11 end
 12 for j in pairs(blacklist) do
 13   if blacklist[j] == ip[1] then
 14     ngx.exit(403)
 15     return  ngx.eof()
 16   end
 17 end

程序清单4-12 whitelist.lua文件


  1 local cjson = require "cjson"
  2 local res = ngx.location.capture('/query?type=white')
  3 local body = res.body
  4 local body_table = cjson.decode(body)
  5 local ip = {ngx.var.remote_addr}
  6 whitelist = {}
  7 for i in pairs(body_table) do
  8   iplist = body_table[i]
  9   local whiteip = iplist["address"]
 10   table.insert(whitelist, whiteip)
 11 end
 12 for j in pairs(whitelist) do
 13   if whitelist[j] == ip[1] then
 14   else
 15     ngx.exit(403)
 16     return  ngx.eof()
 17   end
 18 end

整个项目使用docker-compose up-d命令启动。启动完成后,我们先进行黑名单测试。测试流程如下。

1)访问/blacklist/demo/test接口,做基准测试。


$ curl -v 127.0.0.1:80/blacklist/demo/test
* About to connect() to 127.0.0.1 port 80 (#0)
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 127.0.0.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: openresty/1.17.8.1
...

2)向数据库插入黑名单(IP=172.19.0.1),IP地址为容器的网关地址。


# 插入黑名单
$ curl http://127.0.0.1:80/insert?ip=172.19.0.1\&type=black\&path=
  /blacklist/demo/test
[{"id":1,"type":"black","address":"172.19.0.1","path":"/blacklist/demo/test"}]
# 验证
$ curl -v 127.0.0.1:80/blacklist/demo/test
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< Server: openresty/1.17.8.1
...

3)进入Docker容器,继续做验证。


$ curl -v 127.0.0.1:80/blacklist/demo/test
* About to connect() to 127.0.0.1 port 80 (#0)
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 127.0.0.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: openresty/1.17.8.1
...

检验完黑名单后,继续测试白名单功能。

4)向数据库插入白名单(IP=172.19.0.1),验证接口。


# 插入白名单
$ curl http://127.0.0.1:80/insert?ip=172.19.0.1\&type=white\&path=
  /whitelist/demo/test
[{"id":2,"type":"white","address":"172.19.0.1","path":"/whitelist/demo/test"}]
# 验证
$ curl -v 127.0.0.1:80/whitelist/demo/test
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: openresty/1.17.8.1
...

5)进入Docker容器,继续做验证。由于请求来源IP换为127.0.0.1,无法正常代理请求。


$ curl -v 127.0.0.1:80/whitelist/demo/test
* About to connect() to 127.0.0.1 port 80 (#0)
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 127.0.0.1
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< Server: openresty/1.17.8.1
...

验证完所有逻辑后,我们使用wrk工具针对黑白名单插件进行压测。压测命令为:wrk-t10-c100-d20s--latencyhttp://127.0.0.1:80/blacklist/demo/test和wrk-t10-c100-d20s--latencyhttp://127.0.0.1:80/whitelist/demo/test。测试报告如表4-3所示。

表4-3 OpenResty接口测试报告

可以发现,自定义的黑白名单插件的性能损耗还是比较高的。其主要原因有两点,一是每次代理请求时都会去数据库查询黑白名单信息;二是对查询到的数据还需进行二次处理。有兴趣的读者可以采用4.4节提到的性能优化技巧对该示例进行优化(主要是引入缓存),并重新进行压测,比较性能是否有所提升。