阿里云日志直接输出到ElK监控的Docker镜像

纪念我当时填的坑.

基础说明

当时公司集体的日志收集开始采用阿里云的日志收集系统了.所以很高兴.我又可以少维护一套东西.但是前提是还是得用Kibana看数据啊.(话说阿里云日志的odps一直没有成功过).
整个项目在AliLogOSSLOGSTASH_DOCKER
而基础项目在Logstash-input-oss

阿里日志OSS收集系统的使用方法
nginx-logstash:  
  image: 'friddle/oss_logstash'
  restart: always
  environment:
    - LANG=C.UTF-8
    - JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/jre
    - LOGSTASH_MAJOR=2.3
    - LS_SETTINGS_DIR=/etc/logstash
    - OSSTYPE=nginx
    - ENDPOINT=<ENDPOINT>
    - ACCESS_KEY_SECRET=<SECRET_KEY>
    - ACCESS_KEY_ID=<SECRET_ID>
    - BUCKET=<BUCKET>
    - PREFIX=<PREFIX>
    - INDEX=<INDEX>
    - COMPRESSION_TYPE=<SNAPPY:NONE>
    - LOGTYPE=<LOGTYPE>
  labels:
    aliyun.scale: '1'
  links:
    - elasticsearch
  volumes:
    - '/tmp/logstash:/tmp/logstash'
    - '/tmp/logstash-input-oss-tomcat:/root/.logstash-input-oss'

elasticsearch:  
  image: elasticsearch:2.4
  labels: 
      aliyun.routing.port_9200: http://elasticsearch
  volumes:
    - /data/:/usr/share/elasticsearch/data
  command: elasticsearch -Des.network.host=0.0.0.0

kibana:  
  image: friddle/kibana:latest
  labels:
    aliyun.routing.port_5601:  log
  links:
    - elasticsearch

你只要把上面这一长串添加到容器服务。
并且修改相应的ACCESSKEY,ACCESSSECRET和其他日志相关的信息。你就可以瞬间得到一个能跑起来能查看日志服务系统。非常方便加给力。
这个日志系统唯一的问题就是延时。因为本生阿里的日志系统的OSS是任务模式的。是定时投递的。实时性不是特别强。

这个就是我们日志的成像图

LogstashOSSInput的来源

我们开始慢慢迁移到阿里的日志收集系统了。因为阿里的日志收集系统比自己维护一套收集系统更加简单方便。 我也当然愿意推这个事情。但是问题是。该有的功能不能少。但是阿里的ODPS一直有问题。无奈放弃了这个方案。所以还是得采用ELK方案。但是数据从哪里来了?GitHub上果然有大神做了一个Logstash的插件。
Logstash-input-oss
感谢大神。虽然代码没有看的特别懂,但是能猜出来是怎么干的。
就是去OSS上把日志文件拿下来。再解析,输出到Logstash上。嗯。顿时非常高兴的直接拿来用。

但是出现了一个非常严肃的问题。插件并没有支持Snappy的压缩格式。而我们的日志收集系统果断采用了压缩格式。
所以自己硬着头皮盲写了jruby。把这个功能加上了。不过代码非常不美观和实用。采用的是直接把文件全读到内存。解压。重写回文件。有大神有时间可以帮忙重写个.

阿里的日志服务是需要你去装客户端。必须ROOT权限。同时阿里的日志收集系统可以投递到OSS服务里面。而我们的日志来源正式基于OSS而不是基于其他东西。

顺便提下。阿里的日志分割采用的是正则。界面提供了很强大的功能。自动正则提取。
但是问题是。虽然很智能,但是没有那么智能。无法自己提取stacktrace的错误日志.又只能又是硬着头皮手动写日志解析的正则。

这是我的Tomcat的日志解析正则,一般能吃下大部分的日志格式:
(\d+-\d+-\d+\s\S+)\s+\[(\w+)\]\[(\S+)\]\s(.*)(?=\(com|\(org|\(mysql|\(xml|\(quartz)(\([^)]+\))(.*)

匹配名字正则
Time(\d+-\d+-\d+\s\S+)
level(\w+)
thread(\S+)
message(.*)(?=\(com|\(org|\(mysql|\(xml|\(quartz)
class(\([^)]+\))
stacktrace(.*)

因为其中class不好判断,我们并不能够以()进行分割判断是不是属于message或者后面的class的内容. 但是class大部分开头都是以com/org/mysql等开头的.所以用一个断言message:(.*)(?=\(com|\(org|\(mysql|\(xml|\(quartz)让message判断到是不是已经匹配到(com开头的或者其他固定开头的内容.来确定message的结尾.

其他的匹配选择文件.配置OSS投递都没什么大问题.
注意亮点:
1. RAM角色这一栏,是需要到访问控制里面的角色管理里面设置的.假如没有的话.就去默认添加一个
2. 填写Endpoint的时候最好使用内网地址.可以节省非常多的流量费用.

一些设置的坑和思路

OSS_Logstash的设置

input {  
    oss {
        type => "${OSSTYPE}"
        endpoint => "${ENDPOINT}"
        access_key_id => "${ACCESS_KEY_ID}"
        access_key_secret => "${ACCESS_KEY_SECRET}"
        bucket => "${BUCKET}"
        prefix => "${PREFIX}"
        compression_type => "${COMPRESSION_TYPE}"
    }
}

filter{  
        json{
           source => "message"
        }
    date {
           match =>["__time__","UNIX"]
        }

}
output {  
    elasticsearch {
        hosts => "elasticsearch:9200"
        index => "${INDEX}-%{+YYYY.MM.dd}"
    }

}

时间是通过date进行修改的

entrypoint脚本.为什么不直接运行命令的原因是ElasticSearch启动的非常慢.所以通过nc命令判断elasticsearch是否启动成功了.然后在执行Logstash的启动命令.

#!/usr/bin/env bash

# Wait for the Elasticsearch container to be ready before starting Logstash.
echo "Stalling for Elasticsearch"  
while true; do  
    nc -q 1 elasticsearch 9200 2>/dev/null && break
done

echo "Starting Logstash"  
exec /opt/logstash/bin/logstash -f /opt/logstash/conf --allow-env

Volume:
主要是ElasticSearch的数据量很大而且不适合在容器内部.所以对/usr/share/elasticsearch/data 来设置Volume,放到宿主机上面.

一些关于ELK的碎碎念

ElasticSearch+Kibana+Logstash.三大组建做成现在用户量最多。配置最容易的日志监控系统。网上有很多关于ELK的搭建的博客。大家自己搜索下基本就可以找到很多。
用Logstash收集日志最大的问题是。Logstash本生用Jruby写的。特别消耗服务器资源。而每一台都配置服务器资源会造成非常大的浪费。

顺便记录下当初做日志收集系统时候自己写的Tomcat收集配置文件。在网上并没有找到比较好的实用型配置,还是自己手动写的。表示网上没有很好的Tomcat的日志通用解析解释有关的问题或者博客,觉得很奇怪。

filter {  
    multiline {
      pattern => "^[\d]{4}\-[\d]{2}\-[\d]{2} "
      negate => true
      what => previous
     }
    if "_grokparsefailure" in [tags] {
      drop { }
    }
    grok {
      match => {
         "message" => "%{DATESTAMP:time} \[(?<priority>\w+\s*)\](?<thread>\[\S+\])(?<message>[^(].+) \((?<file>[^)]+)\)(?<stack_trace>.*$)"
         "path" => "%{WORD:application}.log"
      }
      overwrite => [ "message" ]
    }

    if !("_grokparsefailure" in [tags]) {
      date {
        match => [ "time", "YY-MM-dd HH:mm:ss,SSS"]
      }
    }
   if([stack_trace] == "" or [stack_trace] == " ")
   {
     mutate {remove_field=>["stack_trace"] }
   }
}

简单说两句: 一:因为Tomcat的日志并不是单行输出的。不像nginx一样。Tomcat的错误有时候会打印错误的Stack,所以你必须要有一个方式来分割日志行。这个方式就是

   multiline {
      pattern => "^[\d]{4}\-[\d]{2}\-[\d]{2} "
      negate => true
      what => previous
     }

pattern指的是,每条日志的开头必须匹配的正则。^[\d]{4}\-[\d]{2}\-[\d]{2} 明显是时间匹配的正则。解析不了的行是输出上一行的what=>previous

 match => {
         "message" => "%{DATESTAMP:time} \[(?<priority>\w+\s*)\](?<thread>\[\S+\])(?<message>[^(].+) \((?<file>[^)]+)\)(?<stack_trace>.*$)"
         "path" => "%{WORD:application}.log"
      }
      overwrite => [ "message" ]
}

解析日志格式。采用Grok的匹配模式。跟正则很像。是一个较为复杂的正则版本。 (?<priority>\w+\s*)意味着在上下文中,这一段正则\w+\s*匹配到的字符串是priority. 同样也可以另外一种方式%{DATESTAMP:time}:DATESTAMP是定义的Grok的Pattern。后面是DATESTAMP这个规则匹配到的字符串是time

具体匹配规则强烈推荐下面这两个网站:
GrokPattern地址
GrokDebugger地址
这两个网站对于Logstash的匹配测试特别方便。

在ElasticSearch中.默认所有字段都是String.但是有些字段并不想配置成String而是配置成相应的数字格式的时候.需要设置template.

curl -XPOST http://elasticsearch.friddle.me/_template/nginx  
{
  "template": "nginx*",
  "settings": {
    "number_of_shards": 1
  },
  "mappings": {
    "nginx": {
      "properties": {
          "latency": {
                    "type": "float",
                    "store": "yes"
                },
           "request_length": {
                    "type": "integer",
                    "store": "yes"
                },
            "http_version": {
                    "type": "string",
                    "store": "yes"
                },
          "path": {
                    "type": "string",
                    "index": "not_analyzed",
                     "store": "yes"
                },
          "referer": {
                    "type": "string",
                    "index": "not_analyzed",
                     "store": "yes"
                },
          "user_agent": {
                    "type": "string",
                    "index": "not_analyzed",
                     "store": "yes"
                },
          "upstream": {
                    "type": "string",
                    "index": "not_analyzed",
                     "store": "yes"
                },
          "method": {
                    "type": "string",
                    "index": "not_analyzed",
                    "store": "yes"
                }
      }
    }
  }
}

这一段代码默认告诉ElasticSearch.这些index默认使用这个Template("template": "nginx*",)同时.在Mapping设置
这些字段的类型(type).是否进行语法分析(index)和正常查询这些数据是否返回(store)

friddle

继续阅读此作者的更多文章