EFK日志收集丢失率测试报告

目前我们EFK日志系统接入了近10种日志类型,每天的日志量约1T,峰值时达到33W/s的写入,为了更好的服务,我对日志收集的完整度做了一次测试,现发出来,供大家参考!

测试场景:
两种日志:
一种用户访问日志(nginx access log)
一种接口调用日志(nginx access log)

测试方法:
每种日志每个场景做三次测试取平均值

测试逻辑 :

EFK_write_test
添加有策略有:
rewrite_tag_filter
grep
ua_parser
replace
record_modifier
geoip
以下是测试结果对比图

给EFK日志系统添加kafka队列中遇到的time问题解决

以下是我在生产环境中的EFK 架构

efk

在添加kafka队列中的一个问题解决过程

在fluentd中使用fluent-plugin-kafka收集日志到kafka中,日志正常;
然后使用 elasticsearch-river-kafka 来把kafka中的数据写入到ES中,查看正常;
再使用kibana 来读取ES中的数据来展示,问题来了,怎么调试也不能成功的展示数据;

问题在于 fluent-plugin-kafka 默认没有收集日志中的time字段,打开 output_include_time 选项后
就可出现time字段,但并不是日志中的格式,而是转换成了 unix time(例如:1444961965)
而在es中date字段,首先尝试dateOptionalTime,失败的话会尝试UNIX-MS(例如:1444961965000),但是收集到的日志时间是UNIX time,少了毫秒(少了000),所以计算出的时间是在1970年左右,而我在Kibana默认的time tickper是 最近15分钟,于是乎我把时间拉到了最近50年,看到了数据。

查找网上并没找到解决方法,于是自己动手:

修改 out_kafka.rb 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

config_param :client_id, :string, :default => 'kafka'
config_param :output_data_type, :string, :default => 'json'
config_param :output_include_tag, :bool, :default => false
- config_param :output_include_time, :bool, :default => false
+ #config_param :output_include_time, :bool, :default => false
+ config_param :logstash_format, :bool, :default => false

# poseidon producer options
config_param :max_send_retries, :integer, :default => 3
@@ -130,7 +131,17 @@ def emit(tag, es, chain)
begin
chain.next
es.each do |time,record|
- record['time'] = time if @output_include_time
+ if @logstash_format
+ if record.has_key?("@timestamp")
+ time = Time.parse record["@timestamp"]
+ elsif record.has_key?(@time_key)
+ time = Time.parse record[@time_key]
+ record['@timestamp'] = record[@time_key]
+ else
+ record.merge!({"@timestamp" => Time.at(time).to_datetime.to_s})
+ end
+ end
+ #record['time'] = time if @output_include_time
record['tag'] = tag if @output_include_tag
topic = record['topic'] || self.default_topic || tag
partition_key = record['partition_key'] || @default_partition_key

给kibana4中配置中国地图(添加最新版4.3.1修改方法)

因本人使用的是 fluentd 来收集日志,并不是logstash ,所以,以下是fluentd 中的配置:

下载IP库(这里使用的maxmind的免费版,大家如公司有收费版本直接采用即可)
http://dev.maxmind.com/geoip/legacy/geolite/

1
2
3
4

mkdir -p && cd /etc/td-agent/maps
axel -n 10 http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
gunzip GeoLiteCity.dat.gz

在tg-agent中添加好geoip数据库后,安装fluent的geoip插件

1
2
3
4

/opt/td-agent/embedded/bin/fluent-gem install fluent-plugin-geoip-0.5.1.gem
/opt/td-agent/embedded/bin/fluent-gem install fluent-mixin-rewrite-tag-name-0.1.0.gem
/opt/td-agent/embedded/bin/fluent-gem install fluent-plugin-geoip-0.5.1.gem

#注:这里如果安装失败,可能是网络原因,请直接下载对就包本地安装,我这里就是下载的gem包直接本地安装

在td-agent.conf中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

<match geoip-order.**>       #这里上接上面的tag
type geoip
geoip_lookup_key client_ip #这个是用户真实IP的字段
enable_key_country_code geoip_country
enable_key_city geoip_city
#enable_key_latitude geoip_lat
#enable_key_longitude geoip_lon
<record>                                 #这里是因为本身经纬度坐标是独立的两个字段,这里使用location合并
location ${latitude["client_ip"]},${longitude["client_ip"]}
</record>
remove_tag_prefix geoip-order.
add_tag_prefix es-order.    #这里是新添加的tag
#flush_interval 5s

skip_adding_null_record  true  #遇到值为null的记录,跳过此字段
</match>

重启td-agent

在es的mappings中加入:

1
2
3
4
5
6
7
8
9

"geoip" : {
"type" : "object",
"dynamic": true,
"path": "full",
"properties" : {
"location" : { "type" : "geo_point" }
}
}

重建索引,这里的kibana中应该可以看到如下图中的字段

QQ20150901-4@2x

打开kibana4的index.js ,
路径:kibana/src/public/index.js 约 137475行左右
原先的titleLayer注释替换即可,以下是添加的高德地图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

/*
var tileLayer = L.tileLayer('https://otile{s}-s.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpeg', {
attribution: 'Tiles by <a href="http://www.mapquest.com/">MapQuest</a> &amp;mdash; ' +
'Map data &amp;copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
'<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
subdomains: '1234'
});
*/


//add gaode map by elain
var tileLayer = L.tileLayer('http://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&amp;size=1&amp;scale=1&amp;style=8&amp;x={x}&amp;y={y}&amp;z={z}', {
attribution: 'Tiles by <a href="http://www.mapquest.com/">MapQuest</a> — ' +
'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
'<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
subdomains:["1","2","3","4"],
variants: {
Satellite:{
url: 'http://webst0{s}.is.autonavi.com/appmaptile?style=6&amp;x={x}&amp;y={y}&amp;z={z}'
}
}
});

保存,重新加载

使用tilemap创建地图效果如下:

QQ20150901-3@2x

Elasticsearch 免费认证插件Search-Guard的部署安装及策略配置

QQ20150831-1@2x

背景:

当前es正在被各大互联网公司大量的使用,但目前安全方面还没有一个很成熟的方案,大部门都没有做安全认证或基于自身场景自己开发,没有一个好的开源方案
es官方推出了shield认证,试用了一番,很是方便,功能强大,文档也较全面,但最大的问题是收费的,我相信中国很多公司都不愿去花钱使用,所以随后在github
中找到了search-guard项目,接下来我们一起来了解并部署此项目到我们的ES环境中。

目前此项目只支持到1.6以下的es,1.7 还未支持,所以,我们在ES1.6下来部署此项目

软件版本:
ES 1.6.0
kibana 4.0.2
CentOS 6.3

官网地址:
http://floragunn.com/searchguard

功能特性:
基于用户与角色的权限控制
支持SSL/TLS方式安全认证
支持LDAP认证
支持最新的kibana4
更多特性见官网介绍

目标:
实现用户访问es中日志需要登陆授权,不同用户访问不同索引,不授权的索引无法查看,分组控制不同rd查看各自业务的日志,

部署

#download maven:

1
2
3
4
5
6
7
8

axel -n 10 http://mirror.bit.edu.cn/apache/maven/maven-3/3.3.3/binaries/apache-maven-3.3.3-bin.tar.gz
tar zxvf apache-maven-3.3.3-bin.tar.gz
cd apache-maven-3.3.3/

#git search-guard and build
git clone -b es1.6 https://github.com/floragunncom/search-guard.git
cd search-guard ;/home/work/app/maven/bin/mvn package -DskipTests

#把编译好的包放到一个下载地址(方便于es集群使用,单台测试可不使用这种方案):
http://www.elain.org/dl/search-guard-16-0.6-SNAPSHOT.zip

#在es上以插件方式安装编译好的包

1
2
3

cd /home/work/app/elasticsearch/plugins/
./bin/plugin -u http://www.elain.org/dl/search-guard-16-0.6-SNAPSHOT.zip -i search-guard

#elasticsearch.yml 添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

#################search-guard###################
searchguard.enabled: true
searchguard.key_path: /home/work/app/elasticsearch/keys
searchguard.auditlog.enabled: true
searchguard.allow_all_from_loopback: true #本地调试可打开,建议在线上关闭
searchguard.check_for_root: false
searchguard.http.enable_sessions: true

#配置认证方式
searchguard.authentication.authentication_backend.impl: com.floragunn.searchguard.authentication.backend.simple.SettingsBasedAuthenticationBackend
searchguard.authentication.authorizer.impl: com.floragunn.searchguard.authorization.simple.SettingsBasedAuthorizator
searchguard.authentication.http_authenticator.impl: com.floragunn.searchguard.authentication.http.basic.HTTPBasicAuthenticator

#配置用户名和密码
searchguard.authentication.settingsdb.user.admin: admin
searchguard.authentication.settingsdb.user.user1: 123
searchguard.authentication.settingsdb.user.user2: 123

#配置用户角色
searchguard.authentication.authorization.settingsdb.roles.admin: ["root"]
searchguard.authentication.authorization.settingsdb.roles.user1: ["user1"]
searchguard.authentication.authorization.settingsdb.roles.user2: ["user2"]

#配置角色权限(只读)
searchguard.actionrequestfilter.names: ["readonly","deny"]
searchguard.actionrequestfilter.readonly.allowed_actions: ["indices:data/read/*", "indices:admin/exists","indices:admin/mappings/*","indices:admin/validate/query","*monitor*"]
searchguard.actionrequestfilter.readonly.forbidden_actions: ["indices:data/write/*"]

#配置角色权限(禁止访问)
searchguard.actionrequestfilter.deny.allowed_actions: []
searchguard.actionrequestfilter.deny.forbidden_actions: ["indices:data/write/*"]
#################search-guard###################

#logging.yml 添加

1
2

logger.com.floragunn: DEBUG #开启debug,方便调试

#创建key

1
2
3


echo 'be226fd1e6ddc74b' >/home/work/app/elasticsearch/keys/searchguard.key

#重启es

1
2

/etc/init.d/elasticsearch restart

#配置权限策略如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

curl -XPUT 'http://localhost:9200/searchguard/ac/ac?pretty' -d '
{"acl": [
{
"__Comment__": "Default is to execute all filters",
"filters_bypass": [],
"filters_execute": ["actionrequestfilter.deny"]
}, //默认禁止访问
{
"__Comment__": "This means that every requestor (regardless of the requestors hostname and username) which has the root role can do anything",
"roles": [
"root"
],
"filters_bypass": ["*"],
"filters_execute": []
}, // root角色完全权限
{
"__Comment__": "This means that for the user spock on index popstuff only the actionrequestfilter.readonly will be executed, no other",
"users": ["user1"],
"indices": ["index1-*","index2-*",".kibana"],
"filters_bypass": ["actionrequestfilter.deny"],
"filters_execute": ["actionrequestfilter.readonly"]
}, //user1 用户只能访问index1-*,index2-* 索引,且只有只读权限
{
"__Comment__": "This means that for the user spock on index popstuff only the actionrequestfilter.readonly will be executed, no other",
"users": ["user2"],
"indices": ["index3-*",".kibana"],
"filters_bypass": ["actionrequestfilter.deny"],
"filters_execute": ["actionrequestfilter.readonly"]
} //user2 用户只能访问index3-* 索引,且只有只读权限

]}}

多业务复用同组WEB通过独立upstream来敏捷运维

场景
在大部分企业中都遇到一组服务器多个业务复用的现象,比如以下场景
www.a.com www.b.com 都使用了同一组web服务器,在代理上,我们传统的配置方法是这样的 :

www.a.com.conf

1
2
3
4
5
6
7
8
9
10

……
upstream a_cluster_backend {
server 1.1.1.1:80 weight=1 max_fails=2 fail_timeout=10s;
server 1.1.1.2:80 weight=1 max_fails=2 fail_timeout=10s;
}
……
location / {
proxy_pass http://a_cluster_backend;
}

www.b.mi.com.conf

1
2
3
4
5
6
7
8
9
10

……
upstream b_cluster_backend {
server 1.1.1.1:80 weight=1 max_fails=2 fail_timeout=10s;
server 1.1.1.2:80 weight=1 max_fails=2 fail_timeout=10s;
}
……
location / {
proxy_pass http://b_cluster_backend;
}

然而,这样的话,如果我的 1.1.1.1 这台服务器有故障,要下线,需要我更改两个配置文件,即www.a.com.conf www.b.com.conf

那么问题来了,如果有10+个业务都在复用这一组WEB服务器呢,那就需要修改10+个配置文件了,以下是本人测试的两种方法与大家分享:

方法一:
cluster.conf

1
2
3
4
5

upstream cluster_backend {
server 1.1.1.1:80 weight=1 max_fails=2 fail_timeout=10s;
server 1.1.1.2:80 weight=1 max_fails=2 fail_timeout=10s;
}

www.a.com.conf

1
2
3
4
5

……
location / {
proxy_pass http://cluster_backend;

}

www.b.mi.com.conf

1
2
3
4
5

……
location / {
proxy_pass http://cluster_backend;

}

nginx.conf

1
2
3
4
5

http {
……
include vhosts/*.conf;
}

方法二:
cluster.ini

1
2
3

server 1.1.1.1:80 weight=1 max_fails=2 fail_timeout=10s;
server 1.1.1.2:80 weight=1 max_fails=2 fail_timeout=10s;

www.a.com.conf

1
2
3
4
5
6
7
8

upstream a_cluster_backend {
include vhosts/cluster.ini;


location / {
proxy_pass http://a_cluster_backend;
}

www.b.mi.com.conf

1
2
3
4
5
6
7

upstream b_cluster_backend {
include vhosts/cluster.ini;

location / {
proxy_pass http://b_cluster_backend;
}

nginx.conf

1
2
3
4
5

http {
……
include vhosts/*.conf;
}

注:此方法需要nginx的upstream支持include,但NGINX 默认是不支持的,所以需要更改源码重新编译来支持
vim src/http/ngx_http_upstream.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

306 static ngx_command_t ngx_http_upstream_commands[] = {
307
308 { ngx_string("upstream"),
309 NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,
310 ngx_http_upstream,
311 0,
312 0,
313 NULL },
+ 314 { ngx_string("include"),
+ 315 NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1,
+ 316 ngx_conf_include,
+ 317 0,
+ 318 0,
+ 319 NULL },
320
321 { ngx_string("server"),
322 NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
323 ngx_http_upstream_server,
324 NGX_HTTP_SRV_CONF_OFFSET,
325 0,
326 NULL },
327
328 ngx_null_command
329 };

接下来再分享一个坑
我在测试第一种方法时,发现怎么也通不过语法检测,最后发现是因为我当前测试机vhosts/里我有统一配置nginx_status.conf文件,和这个fastcgi_pass冲突导致,请朋友们测试时一定要注意这里。

1
2
3
4
5
6
7
8
9

location ~ ^/(php-fpm_status|php-fpm_ping)$ {
# fastcgi_pass 127.0.0.1:9000;
include fastcgi.conf;
access_log off;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
,