K8S Fluentd Mongo日志采集

K8S Fluentd Mongo日志采集

项目最近需要对K8S集群的容器日志进行统计采集,再汇聚起来持久化。最近比较火的开源日志方案是EFK(Elasticsearch、Fluentd、Kibana),目前项目只需要采集、存储,所以仅对接Fluentd,没有上E和K。

最终确定的日志方案是Fluentd+MongoDB。

Fluentd踩坑

在K8S源代码k8s.io\kubernetes\cluster\addons\fluentd-elasticsearch中有EFK的配置文件,由于我们只需要Fluentd,所以只需要关注fluentd-es-configmap.yamlfluentd-es-ds.yaml

fluentd-es-configmap.yaml

fluentd-es-configmap.yaml就是Fluentd的配置文件,官方比较复杂,根据我们自己的需求,只需要采集容器日志,所有只需要/var/log/containers/*.log,其他source都可以删除。

    <source>
      @id fluentd-containers.log
      @type tail
      path /var/log/containers/*.log
      pos_file /var/log/es-containers.log.pos
      time_format %Y-%m-%dT%H:%M:%S.%NZ
      tag raw.kubernetes.*
      read_from_head true
      <parse>
        @type multi_format
        <pattern>
          format json
          time_key time
          time_format %Y-%m-%dT%H:%M:%S.%NZ
        </pattern>
        <pattern>
          format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/
          time_format %Y-%m-%dT%H:%M:%S.%N%:z
        </pattern>
      </parse>
    </source>

output

存储方案采用Mongo,参考mongo。这里需要安装Mongo插件,由于我们这里讲flunetd容器化,所以不太好安装插件,在docker hub找了全插件的fluentd容器docker pull theasp/fluentd-plugins

    <match **>
      @type mongo
      database k8s
      collection containers
      host mongo.mind-automl
      port 27017
      <buffer>
        flush_mode interval
        retry_type exponential_backoff
        flush_thread_count 2
        flush_interval 5s
        retry_forever
        retry_max_interval 30
        chunk_limit_size 2M
        queue_limit_length 8
        overflow_action block
      </buffer>
    </match>

fluentd-ds.yaml

fluentd-ds.yaml是运行Fluentd damonset的配置文件,基本不需要修改,可能需要修改imageenv

pos_file

运行Fluentd失败,通过日志发现是pos_file没权限写。先把文件夹权限变成777,然后发现每个node上的pos_file文件权限很诡异,每个node上文件的用户和用户组都不一样。通过id命令发现uid都是1000,这个问题遗留。

log不可读

Fluentd成功运行了,并且已经写入mongo,但是发现读不了容器的日志文件。

容器日志

-rw-r-----+ 1 root root  140 Sep 20 05:17 fd12af59e3c534aae556738ded6e412ebfb23933824b004982513177ef9411bd-json.log

只有root用户可读,结合上面pos_file问题,应该是权限问题。通过阅读fluentd容器的Dockerfile,发现问题应该出在启动脚本上。

Dockerfile

# AUTOMATICALLY GENERATED
# DO NOT EDIT THIS FILE DIRECTLY, USE /Dockerfile.template.erb

FROM alpine:3.7
LABEL maintainer "TAGOMORI Satoshi <tagomoris@gmail.com>"
LABEL Description="Fluentd docker image" Vendor="Fluent Organization" Version="1.1"
ENV DUMB_INIT_VERSION=1.2.0

ENV SU_EXEC_VERSION=0.2

ARG DEBIAN_FRONTEND=noninteractive
# Do not split this into multiple RUN!
# Docker creates a layer for every RUN-Statement
# therefore an 'apk delete' has no effect
RUN apk update \
 && apk upgrade \
 && apk add --no-cache \
        ca-certificates \
        ruby ruby-irb \
        su-exec==${SU_EXEC_VERSION}-r0 \
        dumb-init==${DUMB_INIT_VERSION}-r0 \
 && apk add --no-cache --virtual .build-deps \
        build-base \
        ruby-dev wget gnupg \
 && update-ca-certificates \
 && echo 'gem: --no-document' >> /etc/gemrc \
 && gem install oj -v 2.18.3 \
 && gem install json -v 2.1.0 \
 && gem install fluentd -v 0.12.43 \
 && apk del .build-deps \
 && rm -rf /var/cache/apk/* \
 && rm -rf /tmp/* /var/tmp/* /usr/lib/ruby/gems/*/cache/*.gem

# for log storage (maybe shared with host)
RUN mkdir -p /fluentd/log
# configuration/plugins path (default: copied from .)
RUN mkdir -p /fluentd/etc /fluentd/plugins

COPY fluent.conf /fluentd/etc/
COPY entrypoint.sh /bin/
RUN chmod +x /bin/entrypoint.sh


ENV FLUENTD_OPT=""
ENV FLUENTD_CONF="fluent.conf"

ENV LD_PRELOAD=""
ENV DUMB_INIT_SETSID 0
EXPOSE 24224 5140

ENTRYPOINT ["/bin/entrypoint.sh"]

CMD exec fluentd -c /fluentd/etc/${FLUENTD_CONF} -p /fluentd/plugins $FLUENTD_OPT

/bin/entrypoint.sh

#!/usr/bin/dumb-init /bin/sh

uid=${FLUENT_UID:-1000}

# check if a old fluent user exists and delete it
cat /etc/passwd | grep fluent
if [ $? -eq 0 ]; then
    deluser fluent
fi

# (re)add the fluent user with $FLUENT_UID
useradd -u ${uid} -o -c "" -m fluent
export HOME=/home/fluent

# source vars if file exists
DEFAULT=/etc/default/fluentd

if [ -r $DEFAULT ]; then
    set -o allexport
    source $DEFAULT
    set +o allexport
fi

chown home and data folder
chown -R fluent /home/fluent
chown -R fluent /fluentd

exec gosu fluent "$@"

很明显,fluentd容器创建了uid 1000的fluent用户,导致了一系列的权限问题。

解决之道

找到问题原因,解决方案有两个,一个是让1000用户有权限读容器日志,另一个是修改容器启动脚本。第一个方案需要改所有node,比较繁琐,所以采用第二种方法。

第二种方法直接暴力将脚本关于权限命令全部注释

#!/usr/bin/dumb-init /bin/sh

uid=${FLUENT_UID:-1000}

# check if a old fluent user exists and delete it
cat /etc/passwd | grep fluent
if [ $? -eq 0 ]; then
    deluser fluent
fi

# (re)add the fluent user with $FLUENT_UID
# useradd -u ${uid} -o -c "" -m fluent
export HOME=/home/fluent

#source vars if file exists
DEFAULT=/etc/default/fluentd

if [ -r $DEFAULT ]; then
    set -o allexport
    source $DEFAULT
    set +o allexport
fi

# chown home and data folder
# chown -R fluent /home/fluent
# chown -R fluent /fluentd

# exec gosu fluent "$@"
exec "$@"

修改好之后,重新commit新的容器镜像,经测试发现,可以用了。很棒。

fluent-plugin-k8s

虽然fluentd可以读取容器日志,并且存储到mongo里面了,但是日志内容不符合

{"log":"[info:2016-02-16T16:04:05.930-08:00] Some log text here\n","stream":"stdout","time":"2016-02-17T00:04:05.931087621Z"}

问题:

  1. 没有k8s相关信息;
  2. 由于采用tail,所以每次只读一行,根本无法聚合成完整的log文件。

想了很多方法,都没办法解决上面的问题(之前没接触过fluentd),只能回到原点,重头看k8s.io\kubernetes\cluster\addons\fluentd-elasticsearch\fluentd-es-configmap.yaml(大赞老外的注释),由于之前我觉得这个配置文件内容太多,没必要,所以只截取了自己需要的部分。

fluentd-es-configmap.yaml上面大段的注释中,详细说道了我的问题,普通的json信息都非常缺失,所以K8S提供了Kubernetes fluentd plugin解决这个问题,大赞。参考fluent-plugin-kubernetes_metadata_filter

kind: ConfigMap
apiVersion: v1
metadata:
  name: fluentd
  namespace: kube-system
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
data:
  k8s.conf: |-
    <source>
      @type tail
      path /var/log/containers/*.log
      pos_file /var/log/fluentd/fluentd-docker.pos
      time_format %Y-%m-%dT%H:%M:%S
      tag kubernetes.*
      format json
      read_from_head true
    </source>

    <filter kubernetes.var.log.containers.**.log>
      @type kubernetes_metadata
      ca_file /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      bearer_token_file /var/run/secrets/kubernetes.io/serviceaccount/token
    </filter>

    <match **>
      @type mongo
      database k8s
      collection containers
      host mongo.mind-automl
      port 27017
      <buffer>
        flush_mode interval
        retry_type exponential_backoff
        flush_thread_count 2
        flush_interval 5s
        retry_forever
        retry_max_interval 30
        chunk_limit_size 2M
        queue_limit_length 8
        overflow_action block
      </buffer>
    </match>

最终配置如上,这里采用serviceaccount访问K8S集群,所以需要配置ca_file以及bearer_token_file,对serviceaccount不清楚的同学,可以参考之前的文章Pod内进程访问k8s服务