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节提到的性能优化技巧对该示例进行优化(主要是引入缓存),并重新进行压测,比较性能是否有所提升。