Kong使用kafka-log插件推送日志到kafka

4,504 阅读4分钟

议题

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

kong-kafka-elk方案.png

前传

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内部实现如下

80d16e11d2750d6e4127445c126c9f06.png

用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-loglua-resty-kafka可以组成我预期的kong推送日志到kafka的过程。

实践一

  1. 在kong的容器中安装lua-resty-kafka
luarocks install lua-resty-kafka

image-20200330171002942.png

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

image-20200330165352316.png

  1. 修改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

  1. 配置插件,填写kafka相关的信息

image-20200330165634510.png

这些配置是kong-plugin-kafka-log里提供的,后面可以根据需求来改造。

在KafkaTools中观测到kong的日志成功推送到kafka

image-20200330170825437.png

实践二

搭建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 }
}

服务成功起来之后如下

image-20200330171628312.png

手动创建topic

docker exec kafka \
kafka-topics.sh \
--create --topic kong-kafka-log \
--partitions 1 \
--zookeeper zookeeper:2181 \
--replication-factor 1

最终效果如下,在kibana中可以查询到kong产生的日志

image-20200330171938772.png

image-20200330172033605.png

结论

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