Ingress(Nginx)日志持久化与可视化(多图预警)

2,022 阅读6分钟

前言

早期我们通常会使用goaccess或awstat来对nginx等访问日志进行分析和统计,但随着统计分析的多样性以及后续访问日志的实时监控等定制化的需求越来越强烈,goaccess或awstat越来越不能满足我们的需求.所以我们急迫需要更加灵活的日志统计分析工具,能辅助我们对访问日志进行统计、分析和监控.这时候,随着elk/efk的广泛应用,nginx等访问日志也将纳入到elk体系当中,同时elk也能满足我们对日志的统计与分析、监控的多样化需求.

先上图
Alt text
Alt text
Alt text
Alt text
Alt text

部署架构

如图,以下是1个很简单的架构,也没有做缓冲和聚合,如果对日志的要求比较高,可以在中间加入redis或Kafka 等.

Alt text

为什么ingress或者nginx的日志要转换成json格式呢?
我这边简单的解释一下:,主要是2个原因:
1:便于结合elasticseach做实时监控和报警.
比如直接监控status字段,如果1分钟连续出现20次4XX报错就直接报警,如果不转换的化,你还需要进一步解析access日志,这样带来了很多的不便.
2:便于结合elk做可视化分析.
可以组合不同的字段,对不同的需求定制不同的可视化报表.

部署步骤

一、ingress持久化步骤

1. 自建kubernetes的ingress持久化

ingress部署参考: www.pvcreate.com/index.php/a…
Alt text

(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      

Alt text

(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中.

Alt text

(1) 通过阿里云控制台为ingress添加nas存储

Alt text
Alt text
Alt text

(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的安装配置此处不再说明.
添加索引按照界面一步步操作即可.
Alt text
Alt text

几个典型图形配置示例
Alt text

(1)PV
Alt text
(2)UV
Alt text
(3)Top10(接口访问量)
Alt text
Alt text
(4)Top10(客户端IP访问占比)
Alt text
Alt text
(5)Top10(最慢接口)
Alt text
Alt text
(6)后端upstream占比
Alt text
Alt text
(7)实时流量
Alt text
Alt text
(8)客户端访问占比
Alt text
Alt text
(9)平均并发数
Alt text
Alt text
(10)异常状态码统计
Alt text
Alt text
Alt text
(11)总流量
Alt text
(12)接口异常响应码
Alt text
Alt text
Alt text
(13)接口访问耗时占比
Alt text
Alt text
Alt text
(14)每10秒接口访问平均耗时
Alt text
Alt text
(15)每10秒接口访问最大耗时
Alt text
Alt text
(16)状态码统计
Alt text
Alt text
(17)访问量趋势图
Alt text
Alt text
(18)超过30秒以上的接口
Alt text
Alt text
(19)超过30秒以上的接口出现次数
Alt text
Alt text
Alt text

五、踩坑指南

可视化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"      
  }      
}'