议题
考察Kong通过插件推送日志到kafka,ELK从kafka收集日志,并做存储和展示。

前传
Kong官网的Plugins Hub有Kafka Log插件。然后引发一个坏消息和一个好消息。
- 坏消息是可惜只有Kong的企业版才能用。
- 好消息是在github上找到了一个叫kong-plugin-kafka-log的项目,通过对比该开源插件的Readme文档和Kong企业版Kafka Log插件的文档,我怀疑这俩应该是一个东西,或者说,最初是一个项目。不负责任地推测一下,可能是kong-plugin-kafka-log插件被Kong集成进企业版之后,作者不再去更新github上这个开源的插件项目了。
查阅kong-plugin-kafka-log插件的介绍文档,适配的版本信息:Kong >= 0.14.x,结合提交日期,这应该是Kong早期的插件,肯定不能无缝适配当前Kong最新的版本(最新版本2.0.2)。 因此打算基于此开源插件,进行二次开发,并且适配当前选择的Kong的版本。
我基于kong-plugin-kafka-log插件的源码做了更新,适配kong-2.0.x版本,具体可参考kafka-log日志代码解析,更新后的kafka-log代码
开始
翻了一下kong-plugin-kafka-log的源码,其中最关注的是下面这行代码
local ok, err = producer:send(conf.topic, nil, cjson_encode(message))
这个是真正执行发送的代码,我预期的是插件通过调用openresty提供的cosocket API来完成发送和接收网络请求,cosocket是openresty的核心和精髓。
以下来自openresty专栏作者温铭的解释: cosocket 是 OpenResty 中的专有名词,是把协程和网络套接字的英文拼在一起形成的,即 cosocket = coroutine + socket。所以,你可以把 cosocket 翻译为“协程套接字”。cosocket 不仅需要 Lua 协程特性的支持,也需要 Nginx 中非常重要的事件机制的支持,这两者结合在一起,最终实现了非阻塞网络 I/O
cosocket内部实现如下

用cosocket API能达到的效果是:不阻塞处理正常请求的主流程,通过lua协程把日志信息推送到kafka,即以空间换时间。
继续回到上面那段代码,我发现kong-plugin-kafka-log中并没有定义send这个方法,追踪下去发现引用了lua-resty-kafka这个openresty库,顺带看了一下作者,坐标广东,openresty的成员之一,说明这个库可以正常使用。
lua-resty-kafka的文档中声明了
lua-resty-kafka - Lua kafka client driver for the ngx_lua based on the cosocket API
在其代码中真正调用cosocket的地方是broker.lua里面
local tcp = ngx.socket.tcp
……
function _M.send_receive(self, request)
local sock, err = tcp()
……
end
而ngx.socket.tcp就是cosocket API中创建TCP对象的入口。
至此,已能看出kong-plugin-kafka-log和lua-resty-kafka可以组成我预期的kong推送日志到kafka的过程。
实践一
- 在kong的容器中安装
lua-resty-kafka
luarocks install lua-resty-kafka

安装完成后在kong的运行时文件夹中可以看到安装的kafka包

- 修改
kong-plugin-kafka-log插件,主要是handler.lua中有个生成uuid的函数已经过时了,换成新的生成uuid的函数。
原来的
--- Computes a cache key for a given configuration.
local function cache_key(conf)
-- here we rely on validation logic in schema that automatically assigns a unique id
-- on every configuartion update
return conf.uuid
end
local function log(premature, conf, message)
……
local cache_key = cache_key(conf)
……
end
修改之后
local uuid = require "kong.tools.utils".uuid
local function log(premature, conf, message)
……
local cache_key = uuid
……
end
安装完插件后,启动kong
- 配置插件,填写kafka相关的信息

这些配置是kong-plugin-kafka-log里提供的,后面可以根据需求来改造。
在KafkaTools中观测到kong的日志成功推送到kafka

实践二
搭建kaflka+zookeeper+logstash+elasticsearch+kibana环境
用docker-compose来起了这些服务
docker-compose.yml
version: '3'
services:
zookeeper:
container_name : zookeeper
image: wurstmeister/zookeeper
ports:
- 2181:2181
- 2888:2888
- 3888:3888
kafka:
container_name : kafka
image: wurstmeister/kafka:2.11-0.11.0.3
depends_on:
- zookeeper
ports:
- "9092"
environment:
KAFKA_ADVERTISED_HOST_NAME: 192.168.137.10
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_LOG_RETENTION_HOURS: "168"
KAFKA_LOG_RETENTION_BYTES: "100000000"
KAFKA_CREATE_TOPICS: "log:1:1"
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'false'
volumes:
- /var/run/docker.sock:/var/run/docker.sock
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.6.1
container_name: elasticsearch
environment:
- xpack.security.enabled=false
- discovery.type=single-node
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
cap_add:
- IPC_LOCK
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
ports:
- 9200:9200
- 9300:9300
kibana:
container_name: kibana
image: docker.elastic.co/kibana/kibana:7.6.1
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
ports:
- 5601:5601
depends_on:
- elasticsearch
logstash:
container_name: logstash
image: docker.elastic.co/logstash/logstash:7.6.1
volumes:
- "./logstash.conf:/config-dir/logstash.conf"
restart: always
command: logstash -f /config-dir/logstash.conf
ports:
- "9600:9600"
- "7777:7777"
depends_on:
- elasticsearch
- kafka
volumes:
elasticsearch-data:
driver: local
logstash.conf
input {
kafka {
bootstrap_servers => "kafka:9092"
client_id => "logstash"
group_id => "logstash"
consumer_threads => 1
topics => ["kong-kafka-log"]
codec => "json"
tags => ["log", "kafka_source"]
type => "log"
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "logstash-%{[type]}-%{+YYYY.MM.dd}"
}
stdout { codec => rubydebug }
}
服务成功起来之后如下

手动创建topic
docker exec kafka \
kafka-topics.sh \
--create --topic kong-kafka-log \
--partitions 1 \
--zookeeper zookeeper:2181 \
--replication-factor 1
最终效果如下,在kibana中可以查询到kong产生的日志


结论
Kong通过插件推送日志到kafka,ELK从kafka收集日志,并做存储和展示,这条路是可以走通的。需要在现有的开源代码上根据需求进行二次开发。