Ingress(Nginx)日志持久化与可视化(多图预警)
前言
早期我们通常会使用goaccess或awstat来对nginx等访问日志进行分析和统计,但随着统计分析的多样性以及后续访问日志的实时监控等定制化的需求越来越强烈,goaccess或awstat越来越不能满足我们的需求.所以我们急迫需要更加灵活的日志统计分析工具,能辅助我们对访问日志进行统计、分析和监控.这时候,随着elk/efk的广泛应用,nginx等访问日志也将纳入到elk体系当中,同时elk也能满足我们对日志的统计与分析、监控的多样化需求.
部署架构
如图,以下是1个很简单的架构,也没有做缓冲和聚合,如果对日志的要求比较高,可以在中间加入redis或Kafka 等.
为什么ingress或者nginx的日志要转换成json格式呢?
我这边简单的解释一下:,主要是2个原因:
1:便于结合elasticseach做实时监控和报警.
比如直接监控status字段,如果1分钟连续出现20次4XX报错就直接报警,如果不转换的化,你还需要进一步解析access日志,这样带来了很多的不便.
2:便于结合elk做可视化分析.
可以组合不同的字段,对不同的需求定制不同的可视化报表.
部署步骤
一、ingress持久化步骤
1. 自建kubernetes的ingress持久化
ingress部署参考: https://www.pvcreate.com/index.php/archives/205/
(1) ingress添加PVC用于ingress日志存储
kubectl apply -f ingress-nfs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ingress-nfs
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: nfs-client-local
(2) ingress添加挂载
kubectl edit deployments.apps ingress-nginx-controller
......
volumeMounts:
- mountPath: /data/log/ingress/
name: ingress-nfs
......
volumes:
- name: ingress-nfs
persistentVolumeClaim:
claimName: ingress-nfs
(3) ingress修改日志格式和存储位置
kubectl edit configmaps ingress-nginx-controller
access-log-path: /data/log/ingress/access_$hostname.log
error-log-path: /data/log/ingress/error.log
log-format-upstream: '{"@timestamp": "$time_iso8601","remote_addr": "$remote_addr","x-forward-for":
"$proxy_add_x_forwarded_for","request_id": "$req_id","remote_user": "$remote_user","bytes_sent":
$bytes_sent,"request_time": $request_time,"status": $status,"vhost": "$host","request_proto":
"$server_protocol","path": "$uri","request_query": "$args","request_length": $request_length,"duration":
$request_time,"method": "$request_method","http_referrer": "$http_referer","http_user_agent":
"$http_user_agent","upstream-sever":"$proxy_upstream_name","proxy_alternative_upstream_name":"$proxy_alternative_upstream_name","upstream_addr":"$upstream_addr","upstream_response_length":$upstream_response_length,"upstream_response_time":$upstream_response_time,"upstream_status":$upstream_status}'
2. 阿里云kubernetes的ingress持久化
由于阿里云kubernetes上的ingress默认已经部署,同时官方也是建议使用AliyunLogConfig自动接入日志服务和可视化.我们考虑到自定义以及其他原因,采用了自定义接入ingress日志存储,也就是说将ingress存储到nas中,同时发送到elasticsearch中.
(1) 通过阿里云控制台为ingress添加nas存储
(2) ingress添加挂载
kubectl edit deployments.apps -n kube-system nginx-ingress-controller
...
volumeMounts:
- mountPath: /data
name: nfs-oss
...
volumes:
- name: nfs-oss
persistentVolumeClaim:
claimName: ingress-log
(3) ingress修改日志格式和存储位置
kubectl edit configmaps ingress-nginx-controller
修改内容和上面自建kubernetes的ingress一致,需要注意的是如果ingress的日志路径定义为/data/log/ingress/access.log
,一定要注意挂载的目录要存在,也就是说你在修改configmaps之前要确保/data/log/ingress
提前创建,可以进入pod中创建,也可以在外部创建好.
二、Nginx日志格式修改
除了ingress以外,如果你的nginx也需要同步推送到elasticsearch中的话,也需要修改nginx的日志格式为json,值得注意的是有部分参数ingress和nginx是不一致的,比如ingress中支持req_id而nginx中没有该参数.同时以下参数是添加到nginx.conf的http全局参数当中,添加在server段中无效的.
vim nginx.conf
log_format json '{"@timestamp": "$time_iso8601","remote_addr": "$remote_addr","x-forward-for":"$proxy_add_x_forwarded_for","remote_user": "$remote_user","bytes_sent":$bytes_sent,"request_time": $request_time,"status": $status,"vhost": "$host","request_proto":"$server_protocol","path": "$uri","request_query": "$args","request_length": $request_length,"duration":$request_time,"method": "$request_method","http_referrer": "$http_referer","http_user_agent":"$http_user_agent","upstream_addr":"$upstream_addr","upstream_response_length":$upstream_response_length,"upstream_response_time":$upstream_response_time,"upstream_status":"$upstream_status"}';
access_log /data/log/nginx/access.log json;
三、filebeat解析Ingress/Nginx日志
filebeat的安装很简单,这边就不做赘述,我这边主要贴下filebeat的配置文件.
filebeat.yml
setup.template.name: "local-app"
setup.template.pattern: "local-app-*"
setup.template.enabled: true
setup.ilm.enabled: false
filebeat.config:
inputs:
path: /data/www/apps/filebeat/inputs.d/*.yml
reload.enabled: true
reload.period: 10s
modules:
path: /data/www/apps/filebeat/modules.d/*.yml
reload.enabled: false
output.elasticsearch:
protocol: "https"
ssl.verification_mode: none
hosts: ['192.168.1.100:9200']
username: "elastic"
password: "123456"
index: "local-app-%{[fields.appName]}-%{+yyyy.MM.dd}"
nginx_ingress.yml
- type: log
enable: true
tail_files: true
# 如果设置为true,Filebeat从文件尾开始监控文件新增内容,把新增的每一行文件作为一个事件依次发送,
# 而不是从文件开始处重新发送所有内容
paths:
- /data/log/nginx/access*.log
tags: [app, nginx-local]
fields:
appName: nginx-local
json.keys_under_root: true
#keys_under_root可以让字段位于根节点,默认为false
json.overwrite_keys: true
#对于同名的key,覆盖原有key值
json.message_key: message
#message_key是用来合并多行json日志使用的,如果配置该项还需要配置multiline的设置,后面会讲
json.add_error_key: true
#将解析错误的消息记录储存在error.message字段中
json.ignore_decoding_error: true
#用于指定是否JSON解码错误应该被记录到日志中。如果设为true,错误将被记录
要注意的是,如果配置了multiline,会开启合并多条json日志的功能,如果不需要该功能请务必注释掉该yml中关于multiline的配置
。(由于我在nginx/ingress中的access的json日志都是一行,所以在nginx日志当中不需要配置multiline配置)
multiline配置:
#将'['作为新的一行的标识,如果message中不碰到'[',则合并为一条日志
multiline.pattern: ^\[
multiline.negate: true
multiline.match: after
同时配置:
processors:
- decode_json_fields:
fields: ['message']
target: json
四、kibana接入elasticsearch与可视化配置
kibana的安装配置此处不再说明.
添加索引按照界面一步步操作即可.
(1)PV
(2)UV
(3)Top10(接口访问量)
(4)Top10(客户端IP访问占比)
(5)Top10(最慢接口)
(6)后端upstream占比
(7)实时流量
(8)客户端访问占比
(9)平均并发数
(10)异常状态码统计
(11)总流量
(12)接口异常响应码
(13)接口访问耗时占比
(14)每10秒接口访问平均耗时
(15)每10秒接口访问最大耗时
(16)状态码统计
(17)访问量趋势图
(18)超过30秒以上的接口
(19)超过30秒以上的接口出现次数
五、踩坑指南
可视化Metrics无法获取耗时(duration)字段
以Top10(最慢接口)举例,获取Top10耗时最慢的url组成1个表格,但是我在Metrics怎么都找不到duration字段或者request_time字段,通过排查得知,Metrics字段一般是数值型字段,对数值型字段求和、求最大值、求平均值等.但是我在ingress定义的字段都是字符串,所以同步到elasticsearch中也是字符串,所以在kibana的Metrics中也无法找到duration字段.既然找到问题症结了,我们就开始修正.重新修改ingress的confimap配置,重新在kibana添加索引.当然添加索引之前,我先删除了原来的索引重新添加.当然这个方法比较粗暴!!还有其他方法可以解决.
(1)如果是logstash可以使用mutate对字段进行转换
mutate {
convert => ["name-of-field", "integer"]
}
(2)官方没有提供字符串转数值,但我们可以创建1个新的索引,同时把原来的elasticsearch格式化数值后导入即可.
#创建新索引并格式化duration字段
curl -H 'Content-Type: application/json' -XPUT "http://localhost:9200/ingress_new"
curl -H 'Content-Type: application/json' -XPOST "http://localhost:9200/ingress_new/ingress_old/_mapping?pretty" -d '
{
"ingress_old": {
"properties": {
"duration": {
"type": "double",
"store": "true",
"ignore_malformed": "true"
}
}
}
}
#从旧索引中导入数据
curl -X POST "localhost:9200/_reindex" -H 'Content-Type: application/json' -d'
{
"source": {
"index": "ingress_old"
"size": 5000
},
"dest": {
"index": "ingress_new"
"routing": "=cat"
}
}'