基于容器的分布式日志收集

0. 基础方案:ELK


大规模系统,特别是分布式系统中,服务化、容器化、运维自动化带来了运维的便利,但是相应的代价是系统复杂性上升,开发人员将面对种种复杂情况的挑战。

其中日志的离散将导致调错成本进一步上升,分布式日志的集中收集处理可能是这一问题的解决之道。

业界有多重面向容器的日志解决方案,不再一一复述,ELK 因为其开源且接受过实战考验,作为我们的日志收集方案之一。

ELK 的架构就不展开详解了,因为关于这方面的文章已经有很多,他的总体思路就是:

Logstash(采集) -> Elasticsearch(储存) -> Kibana(查询/显示)

ELK Constructure

这个架构健壮而且每个点都可以水平扩展。在调研后,我们决定采取 Logstash + Elasticsearch 作为日志采集和储存方案,并自己定制日志查询页面。那剩下的问题是,如何把日志塞入 Elasticsearch 中。

1. 方案一:Syslog


1.1 配置与运行

日志在容器中产生后,如何进入 Elasticsearch,这其中又有多种不同的解决办法,而每个办法都各有优劣,在一轮调研后我决定采用 Docker 内建的 docker-driver-syslog

他的好处之一是配置方便,只需要在容器启动时加上参数:

$ docker run \
         --log-driver=syslog \
         --log-opt syslog-address=tcp://localhost:514 \
         --log-opt syslog-facility=daemon \
         ubuntu echo "hello"

然后在 Logstash 中开启 syslog 协议:

##
# logstash.conf
input {  
  syslog {
    port => "5141"
    # codec ...
  }
}

filter {  
  syslog_pri {
    # ...
  }
}

1.2 缺陷分析

但是他的缺点也是明显的:

  1. 与容器进程 docker-containerd 位于一个线程中
    如果 logstash 端口未开启,由于无法建立 syslogtcp 连接从而抛出异常,容器可以创建却无法启动。

  2. 覆盖原有日志驱动选项
    指定 log-driversyslog 后,Docker 默认的 json-file 驱动被覆盖不生效,这意味着不能通过 docker.sock 监听到日志输出,也无法使用 docker logs 命令。

  3. Logstash 集群可用性问题
    当 Logstash 集群出现抖动时,会阻塞 containerd 进程,进而阻塞 dockerd 进程,导致大量日志堆积还引发了一次生产事故。

  4. 负载均衡
    由于使用 tcp:// 协议,在 Logstash 水平扩展和负载均衡问题上还没有找到好办法。

2. 方案二:Filebeat


2.1 构思

显然,图一时方便的 syslog 有诸多不便,也违背监控系统无侵入设计原则,

这让我不得不重新审视当时启用的 Filebeat,Filebeat 基于 Golang 开发,占用资源小,支持多种日志输入输出方式:文件、Redis、Logstash、Kafka 等。

syslog 驱动最大的不同就是 Filebeat 是一个无侵入独立的应用进程,即使失效也不会导致应用连锁崩溃。若担心 Filebeat 监听、解析发送日志会占用系统资源,大可以将它再容器化,限制其内存和 CPU 资源,在云上配置子网走带外传输,不干扰应用本身的带宽。

2.2 配置 Dockerd

首先我们需要配置 Docker Engine 为原来的 json-file 日志驱动加入额外的标签,让我们能够区分不同的应用和生产环境,区分数据来源是关键的一步,否则所有的日志将变成一锅乱炖。

# /lib/systemd/system/docker.service

# more above ....
ExecStart=/usr/bin/dockerd \  
  --log-driver=json-file \
  --log-opt labels=BEAT_EXCLUDE \
  --log-opt env=APP_KEY,PROD_ENV
  # registry and more ...
# more follow ...

在 Debian、CentOS 中,我们编辑 docker.service 描述文件,加入这些描述项,并重启 Docker。其中 BEAT_EXCLUDE 是之后会在配置 Filebeat 是使用的标记项,防止 Filebeat 监听自己的日志。

这样,我们再次运行一个容器,同时加上相应的 label 和 env:

$ docker run -i --rm --label BEAT_EXCLUDE=false -e PROD_ENV=staging debian echo "Hello $(date)"

查看它的日志,它就会带上对应的属性:

{
  "log": "Hello Wed Mar 15 07:21:34 UTC 2017\n",
  "stream": "stdout",
  "attrs": {
    "BEAT_EXCLUDE": "false",
    "PROD_ENV": "staging"
  },
  "time": "2017-03-16T03:51:57.066492296Z"
}

2.3 容器化 Filebeat

将 Filebeat 容器化后,再把 Docker 的数据文件夹挂载到 Filebeat 的容器上,再监听 json-file 日志输出,到这一步,就完成了监控应用端的全部工作。

容器描述如下:

# Dockerfile
FROM debian  
MAINTAINER James Bacon <d@terminus.io>

ENV BEAT_VERSION 5.2.2

RUN apt-get update \  
  && apt-get install curl -y \
  && curl -sSLO https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-${BEAT_VERSION}-linux-x86_64.tar.gz \
  && tar xzf filebeat-${BEAT_VERSION}-linux-x86_64.tar.gz \
  && mv filebeat-${BEAT_VERSION}-linux-x86_64/filebeat /usr/local/bin \
  && rm -rf filebeat-${BEAT_VERSION}-linux-x86_64* \
  && apt-get autoremove curl -y \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

VOLUME /var/lib/docker/containers  
VOLUME /var/logs/filebeat

ENTRYPOINT "filebeat"  
CMD ["-c", "filebeat.yml"]  

并创建启动配置,以指定监听的目录:

# filebeat.yml

filebeat.prospectors:  
- input_type: log
  paths:
    - "/var/lib/docker/containers/*/*json.log"
  encoding: utf-8
  # 不监听自己的日志,否则会产生回音效应导致日志爆炸
  exclude_lines: ["BEAT_EXCLUDE\": \"true"]
  include_lines: ["APP_KEY"]
  tail_files: true
output.logstash:  
  enabled: true
  # 需要 logstash 开启对应端口
  hosts: ["10.0.1.1:5044"]
logging.files:  
  path: /var/logs/filebeat

最后启动 Filebeat 即可:

$ docker run -d --name logstash \
  --label BEAT_EXCLUDE=true \
  -v /var/lib/docker/containers:/var/lib/docker/containers:ro \
  -v /var/logs/filebeat:/var/logs/filebeat:ro \
  --cpus ".5" -m 256m \
  --restart always \
  logstash

3. TODO

即便如此,还需要更多的工作才能让这套架构更加健壮:

  1. beat on parallel
    需要在 Filebeat 中开启多个 worker 来适应生产环境中的海量日志流。

  2. beat on docker.sock
    直接监听 docker.sock 而不是 json.log,进一步精简监控的结构。

  3. beat via Kafka
    Logstash 是基于 JRuby 开发的,有一定性能和资源问题,Filebeat 支持直接将文件发送至 Kafka 的一个 Topic,再通过自定义消费者存入 Elasticsearch。

张成栋

Read more posts by this author.

中国浙江省杭州市

Subscribe to The Terminus Blog

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!