使用graylog来收集你的Docker日志

Docker日志


Docker会默认收集应用程序的标准输出存储到一个json.log文件中,文件的格式类似下面这种:

{"log":"root@c835298de6dd:/# ls\r\n","stream":"stdout","time":"2014-03-14T22:15:15.155863426Z"}
{"log":"bin  boot  dev\u0009etc  home  lib\u0009lib64  media  mnt  opt\u0009proc  root  run  sbin  selinux\u0009srv  sys  tmp  usr  var\r\n","stream":"stdout","time":"2014-03-14T22:15:15.194869963Z"}

以一行一个作为一条JSON数据存储。Docker的这种日志存储方式是可以配置的,具体参数可以在执行run启动容器的时候通过log-driver进行配置,具体配置请参考log-driver。下面是官方的log-driver的配置可选项:

Supported logging drivers

The following logging drivers are supported. See each driver’s section below for its configurable options, if applicable.

Driver Description

none No logs will be available for the container and docker logs will not return any output.
json-file The logs are formatted as JSON. The default logging driver for Docker.
syslog Writes logging messages to the syslog facility. The syslog daemon must be running on the host >machine.
journald Writes log messages to journald. The journald daemon must be running on the host machine.
gelf Writes log messages to a Graylog Extended Log Format (GELF) endpoint such as Graylog or Logstash.
**fluentd ** Writes log messages to fluentd (forward input). The fluentd daemon must be running on the host machine.
awslogs Writes log messages to Amazon CloudWatch Logs.
splunk Writes log messages to splunk using the HTTP Event Collector.
etwlogs Writes log messages as Event Tracing for Windows (ETW) events. Only available on Windows platforms.
gcplogs Writes log messages to Google Cloud Platform (GCP) Logging.

Docker默认使用了json-file driver作为log driver,因此日志是存储在一个json.log的本地文件中的。而gelf则是本文需要使用的log driver。

当容器多了,或者是采用类似swarm集群部署Docker的时候,各种日志分散存在各个json.log文件中,当查找问题或者进行相关统计的时候,分散的日志对我们来说是非常不友好的。我们需要一个能够集中管理Docker日志的工具,这也是本文介绍的重点。

Graylog

相信大部分的人都用过或者听说过ELK这个强大的日志栈架构,我们要用的graylog和ELK非常的相似,但是算是后起之秀,具体对比请参考:

Graylog——日志聚合工具的后起之秀

为什么要选择graylog来作为Docker的日志收集平台呢?

  • Docker原生支持graylog协议,直接将日志发送到graylog(通过gelf协议

  • graylog官方提供了将本身部署在Docker的支持

部署

graylog官方提供了Dockerfile供我们快速的在Docker上部署日志系统,Dockerfile地址,在这个Docker Hub的地址中,也提供了docker-compose.yml来快速部署整个graylog栈,包含了mongodb、ElasticSearch,而不需要分别单独进行部署,我将完整的部署过程总结如下:

  1. 创建一个目录用来部署graylog,本文假设目录为/data/graylog,以下所有操作都是在/data/graylog中进行的。
  2. 初始化目录和配置文件
#创建数据目录
mkdir -p ./graylog/data
#创建配置文件目录
mkdir -p ./graylog/config
cd ./graylog/config
#直接下载官方推荐配置文件
wget https://raw.githubusercontent.com/Graylog2/graylog2-images/2.1/docker/config/graylog.conf
#日志配置文件
wget https://raw.githubusercontent.com/Graylog2/graylog2-images/2.1/docker/config/log4j2.xml

修改下载完的graylog.conf中的root_timezone为:

root_timezone = +08:00

改为GMT+0800中国时区。

  1. 新建docker-compose.yml来供docker-compose快速启动完整服务,文件内容如下:
version: '2'
services:
  some-mongo:
    image: "mongo:3"
    volumes:
      - /etc/localtime:/etc/localtime
      - /etc/timezone:/etc/timezone
      - ./graylog/data/mongo:/data/db
  some-elasticsearch:
    image: "elasticsearch:2"
    command: "elasticsearch -Des.cluster.name='graylog'"
    volumes:
      - /etc/localtime:/etc/localtime
      - /etc/timezone:/etc/timezone
      - ./graylog/data/elasticsearch:/usr/share/elasticsearch/data
  graylog:
    image: graylog2/server:2.1.1-1
    volumes:
      - /etc/localtime:/etc/localtime
      - /etc/timezone:/etc/timezone
      - ./graylog/data/journal:/usr/share/graylog/data/journal
      - ./graylog/config:/usr/share/graylog/data/config
    environment:
      GRAYLOG_PASSWORD_SECRET: somepasswordpepper
      GRAYLOG_ROOT_PASSWORD_SHA2: 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918
      #这个地址需要配置成你要访问的地址,比如你的容器部署在192.168.1.2,你需要通过192.168.1.2来访问服务的话,你需要配置成http://192.168.1.2:9000/api,否则访问会报错
      GRAYLOG_WEB_ENDPOINT_URI: http://127.0.0.1:9000/api
    links:
      - some-mongo:mongo
      - some-elasticsearch:elasticsearch
    ports:
      #web界面端口
      - "9000:9000"
      #gelf收集日志的端口,如果需要添加graylog收集器,可以新增暴露出来的端口
      - "12201/udp:12201/udp"
      - "1514/udp:1514/udp"

文件保存后通过

docker-compoes up

启动整个服务,如果没有问题的话,会看到graylog webserver started的终端输出消息。
访问 http://{server}:9090/
会看到graylog的web界面,使用用户名admin,密码:admin 来登录后台,至此部署完成。

系统配置

  • input配置
    graylog的日志收集通过定义input对象来完成,在graylogweb管理界面按照如下图片进入input对象配置:
进入Input配置界面

选择GELF UDP协议来新建一个输入器(input)

选择正确的Input类型

填好相关属性,新建:

Input详细配置

至此graylog就可以开始收集日志了

  • Docker配置
    如果docker通过命令行启动,可以在run命令中加上如下参数:
docker run --log-driver=gelf --log-opt gelf-address=udp://{graylog服务器地址}:12201  --log-opt tag=<当前容器服务标签,用来供graylog查询的时候进行分类>  <IMAGE> <运行命令>

如果通过docker-compose命令,则可以在docker-compose.yml中加入相关配置,以下用tomcat容器举例:

version: '2'
services:
  tomcat:
    image: tomcat:7
    volumes:
         - /etc/localtime:/etc/localtime
         - /etc/timezone:/etc/timezone
         - /data/tomcat-adtp/logs:/usr/local/tomcat/logs
         - /data/tomcat-adtp/webroot:/usr/local/tomcat/webapps/ROOT
    environment:
         - JAVA_OPTS=-Xmx1g
         - JAVA_OPTS=-Xms3g
    ports:
        - "80:8080"
    logging:
      driver: "gelf"
      options:
        gelf-address: "udp://graylogserver:12201"
        tag: front-tomcat

容器启动的时候可能会有下面这个提示:

tomcat_1  | WARNING: no logs are available with the 'gelf' log driver

可以无视这个警告,日志还是会继续发送过去的。到这里为止我们可以在graylog的web后台中看到tomcat所产生日志了。

后续

虽然日志已经可以集中收集了,但是有些日志还是不会以我们理想的方式收集的,比如Java程序的异常堆栈、Hibernate的格式化sql等等,因为这种日志并不是一行一个的,而是多行一个整体,但是Docker不会知道这个,而是一行一个把日志发送到了graylog,导致一个完整的堆栈分散的存储到了graylog中,这样显然不是我们想要看到的结果,在这里我们可以使用一种比较折衷的方法,让Java程序自己将这种多行日志发送到graylog,剩下的日志继续由Docker处理,具体做法如下:
由于本人在目前系统中使用的logback,可以使用特定的appender把日志发送到graylog,我相信log4j或者是其他相关的日志框架也都有类似的配置。修改logback的配置文件,添加指定的log appender:

<appender name="gelf" class="me.moocar.logbackgelf.GelfUDPAppender">
        <remoteHost>192.168.31.8</remoteHost>
        <port>12201</port>
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="me.moocar.logbackgelf.GelfLayout">
                <!--An example of overwriting the short message pattern-->
                <shortMessageLayout class="ch.qos.logback.classic.PatternLayout">
                    <pattern>%ex{short}%.100m</pattern>
                </shortMessageLayout>
                <!-- Use HTML output of the full message. Yes, any layout can be used (please don't actually do this)-->
                <fullMessageLayout class="ch.qos.logback.classic.PatternLayout">
                    <pattern>%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level \(%F:%L\) - %msg %n</pattern>
                </fullMessageLayout>
                <useLoggerName>true</useLoggerName>
                <useThreadName>true</useThreadName>
                <useMarker>true</useMarker>
                <includeFullMDC>true</includeFullMDC>
                <fieldType>requestId:long</fieldType>
                <!--Facility is not officially supported in GELF anymore, but you can use staticFields to do the same thing-->
                <staticField class="me.moocar.logbackgelf.Field">
                    <key>tag</key>
                    <value>business-server</value>
                </staticField>
            </layout>
        </encoder>
    </appender>
    <root>
        <level value="info"/>
        <appender-ref ref="gelf"/>
    </root>

这样logback会自动把日志发送到graylog,异常堆栈信息也不会分散了,这个功能需要一个第三方的logback扩展支持,github地址在这里

结束

至此,搭建一个graylog并且收集Docker日志集中管理的方案就完成了,希望能够帮到看这篇文章的你。

推荐阅读更多精彩内容