kubernetes上的日志收集和日志清理

kubernetes上的日志收集和日志清理

在日益成熟的微服务架构中,可以成功地将微服务迁移到 kubernetes上,同时日志收集和日志清理成为了必须解决地问题。

日志收集工具ELK

ELK分别指Elasticsearch , Logstash, Kibana三个开源软件。同时还有一个FileBeat,它是一个轻量级的日志收集处理工具(Agent),Filebeat占用资源少,适合于在各个服务器上搜集日志后传输给Logstash 。以及一个 Kibana 插件 logtrail 用于即时查看日志。

对于 kubernetes 集群来说,就是先由 Filebeat 收集日志,然后传输到 Logstash 集群中,Logstash 集群对日志信息做处理后,存储在 Elasticsearch 数据库中。在 Kibana 界面中,开发人员可以通过 logtrail 查看即时的日志,也可以对整体日志情况有一个图形化的展示和监测。通过以上组件就可以实现日志的筛选和收集,并能即时查看日志信息。

kubernetes上的实现

kubernetes 常见的有3种解决方式。一是直接对 console 吐出的日志进行处理,如果没有重定向,默认在 /var/log/containers 下有软连接到日志文件。二是对每一个 pod 添加一个 Agent ,然后Agent 收集日志,但是这种处理方式开销较大。三是以 DaemonSet 的形式,在每个 node 下执行一个 Agent ,Agent 通过 hostpath 挂载在 node 上,同时微服务也通过 hostpath 挂载在这个路径下,Agent 在这个路径下日志收集。

DaemonSet

DaemonSet 确保每一个 Node 节点都运行一个 Pod 的副本。也就是说,我们将微服务的日志,以 hostPath 的形式,挂载在 node 的一个路径(/app/log)下。再以 DaemonSet 的形式,在每一个 Node 下启动一个 Agent ,也就是 Filebeat ,同样 Filebeat 以 hostPath 的形式,挂载 node 的路径(/app/log),将日志收集起来,并传输到日志处理的集群中。

avatar

日志清理

日志的定时清理,主要思路是每天将前一天的日志文件打包成压缩文件,然后每次都定时删除 n 天前的日志压缩文件。

docker

kubernetes 是基于 docker 的。docker 有自己的日志清理机制。

vi /etc/docker/daemon.json

daemon.json

{
  "log-driver":"json-file",
  "log-opts":{
    "max-size" :"2g","max-file":"8"
    }
}

service docker restart

logback-spring.xml

将日志文件转成 json 的格式作为 Filebeat 的收集源,并定期清理。

    <!-- Appender to log to file -->
    <appender name="flatFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
            <maxHistory>8</maxHistory>
            <totalSizeCap>2GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>
    <!-- Appender to log to file in a JSON format -->
        <appender name="filebeat" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_FILE}.json</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_FILE}.json.%d{yyyy-MM-dd}.gz</fileNamePattern>
                <maxHistory>8</maxHistory>
                <totalSizeCap>2GB</totalSizeCap>
            </rollingPolicy>
            <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
                <providers>
                    <timestamp>
                        <timeZone>UTC</timeZone>
                    </timestamp>
                    <pattern>
                        <pattern>
                            {
                            "severity": "%level",
                            "service": "${springAppName:-}",
                            "trace": "%X{X-B3-TraceId:-}",
                            "span": "%X{X-B3-SpanId:-}",
                            "parent": "%X{X-B3-ParentSpanId:-}",
                            "exportable": "%X{X-Span-Export:-}",
                            "pid": "${PID:-}",
                            "thread": "%thread",
                            "class": "%logger{40}",
                            "rest": "%message %ex{6}"
                            }
                        </pattern>
                    </pattern>
                </providers>
            </encoder>
        </appender>

logrotate

github仓库:https://github.com/blacklabelops/logrotate

这里提供了一个 logrotate 的 DaemonSet 实现方式。kubernets 会将日志软连接到 /var/log/containers ,logrotate 主要是对 /var/log/containers 的清理。

kubectl create -f logrotate.yaml

logrotate.yaml

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: logrotate
spec:
  template:
    metadata:
      labels:
        app: logging
        id: logrotate
      name: logrotate
    spec:
      containers:
      - name: logrotate-es
        image: blacklabelops/logrotate
        securityContext:
          privileged: true
        volumeMounts:
         - name: varlog
           mountPath: /some-dir
         - name: logs
           mountPath: /logs
        env:
        - name: LOGS_DIRECTORIES
          value: "/some-dir"
        - name: LOGROTATE_INTERVAL
          value: "daily"
        - name: LOGROTATE_OLDDIR
          value: "/logs"
      volumes:
         - hostPath:
         - hostPath:
             path: /some-dir
           name: varlog
         - hostPath:
             path: /app/logs
             type: DirectoryOrCreate
           name: logs

实现步骤&踩坑

基于helm安装,官方提供的 charts ,github仓库: https://github.com/helm/charts/tree/master/stable,基于仓库提供的 charts 修改。

hostpath

首先微服务需要将日志文件挂载在宿主机上。

deployment.yaml

        volumeMounts:
          - hostPath:
              path: /app/log
              type: DirectoryOrCreate
            name: applog
      volumes:
        - name: applog
          mountPath: /app/log

其中 DirectoryOrCreate 是必要的,要让 pod 具有创建文件目录的权限于宿主机上。

Value Behavior
  Empty string (default) is for backward compatibility, which means that no checks will be performed before mounting the hostPath volume.
DirectoryOrCreate If nothing exists at the given path, an empty directory will be created there as needed with permission set to 0755, having the same group and ownership with Kubelet.
Directory A directory must exist at the given path
FileOrCreate If nothing exists at the given path, an empty file will be created there as needed with permission set to 0644, having the same group and ownership with Kubelet.
File A file must exist at the given path
Socket A UNIX socket must exist at the given path
CharDevice A character device must exist at the given path
BlockDevice A block device must exist at the given path
filebeat

首先 helm/charts 官方提供的镜像源有点问题。需要将镜像源 docker.elastic.co/beats/filebeat-oss 改成 docker.elastic.co/beats/filebeat

同时由于不同的镜像源,需要修改volumeMounts 和 volume 配置文件的位置,将 filebeat-config 的/usr/share/filebeat/filebeat.yml 改成 /home/filebeat-config

        volumeMounts:
        - name: filebeat-config
          mountPath: /home/filebeat-config      
      volumes:
      - name: filebeat-config
        configMap:
          name: 

去掉 initContainers 。

在 values.yaml 加上

args: [
  "-c", "/home/filebeat-config/filebeat.yml",
  "-e",
]

修改 ConfigMap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: 
  labels:
    k8s-app: filebeat
    kubernetes.io/cluster-service: "true"
    app: filebeat-config
data:
  filebeat.yml: |
    processors:
    - drop_fields:
        fields: ["beat.version","beat.name","host","tags","@version","offset","prospector","input"]
    - rename:
        fields:
          - from: "beat.hostname"
            to: "hostname"
    filebeat.inputs:
    - type: log
      paths:
        - /app/log/*/*.json
      json.keys_under_root: true
      json.add_error_key: true
      json.overwrite_keys: true
      fields_under_root: true
      fields:
        logtype: service_log
    output.logstash:
      hosts: ['172.16.20.64:35044']
    logging.level: info  

坑1:

这里必须直接修改 ConfigMap.yaml 而不是 values.yaml。

因为在通过 values.yaml 转化到 ConfigMap.yaml 的时候,helm 的语言引擎会将 [] 这种数组形式,自动转化为yaml的数组形式。

也就是说

fields: ["beat.version","beat.name","host","tags","@version","offset","prospector","input"]

将自动转化为

fields:
  - beat.version
  - beat.name
  - host
  - tags
  - @version
  - offset
  - prospector
  - input

filebeat 将无法识别出这种配置文件。

所以只能直接修改 ConfigMap.yaml 来解决这种问题。

这里是参考Kubernetes部署ELK并使用Filebeat收集容器日志 实现的。

Logstash

使用 helm/charts 官方提供的 chart 部署在 kubernetes 上,以 NodePort 的形式暴露服务在端口 35044 上。

配置文件

inputs:
  main: |-
    input {
      beats {
        port => 5044
      }
    }

filters:

outputs:
  main: |-
    output {
      elasticsearch {
        hosts => ["172.16.20.64:39200"]
        index => "service-%{+YYYY.MM.dd}"
      }
    }
Elasticsearch

helm/charts 官方提供了一个可以实现高可用的实现方案。至少需要 2个 elasticsearch-master ,1个 elasticsearch-client ,1个 elasticsearch-data 。helm/charts 提供的是oss版本的,需要修改镜像。

docker.elastic.co/elasticsearch/elasticsearch-oss 改成 docker.elastic.co/elasticsearch/elasticsearch

其中 pvc data-es-elasticsearch-data-0 挂载的 pv 因为是非 root 用户执行的启动脚本。在对应路径下没有权限执行,无法初始化。

需要 chmod -R 777 pv的路径 给予权限。

Kibana&logtrail

使用 helm/charts 官方提供的方案。其中自带有 logtrail 插件部署的实现方式。

plugins:
  enabled: true
  reset: false
  values:
    - logtrail,0.1.30,https://github.com/sivasamyk/logtrail/releases/download/v0.1.30/logtrail-6.4.2-0.1.30.zip

但是官网提供的 chart 有一个问题,就是无法修改 logtrail 配置文件。如果将目录 /usr/share/kibana/plugins 挂载出来,pod 将卡在 init:0/1 状态无法启动。同样 configMap 也不行,因为 configMap 也是 volumes 的一种实现方式。

在这里提供了一个解决方案。

首先正常 helm 安装 带有 logtrail 插件的 Kibana。

然后 kubectl edit deployments kibana

以下是打算修改的 logtrail.json

{
"index_patterns" : [
    {
      "es": {
        "default_index": "service-*",
        "allow_url_parameter": false
      },
      "tail_interval_in_seconds": 10,
      "es_index_time_offset_in_seconds": 0,
      "display_timezone": "Etc/CST",
      "display_timestamp_format": "YYYY MMM DD HH:mm:ss",
      "max_buckets": 500,
      "default_time_range_in_days" : 0,
      "max_hosts": 100,
      "max_events_to_keep_in_viewer": 5000,
      "fields" : {
        "mapping" : {
            "timestamp" : "@timestamp",
            "display_timestamp" : "@timestamp",
            "hostname" : "service",
            "program": "hostname",
            "message": "rest"
        },
        "message_format": "} | } | }"
      },
      "color_mapping" : {
        "field" : "severity",
        "mapping" : {
          "ERROR": "#ff3232",
          "WARN": "#ff7f24",
          "DEBUG": "#ffb90f",
          "TRACE": "#a2cd5a"
         }
      }
    }
  ]
}

spec.template.spec.containers.initContainers.command的最后加上

echo "压缩转义后的logtrail.json" > /usr/share/kibana/plugins/logtrail/logtrail.json

等待 pod 重启,配置文件就生效了。

坑2:

在这里 echo "压缩转义后的logtrail.json" > /usr/share/kibana/plugins/logtrail/logtrail.json 同样不能直接写入 chart 的 deployment.yaml 。

因为 } 对于 helm 的匹配规则 `` 是适用的,因此不能直接写在 chart 当中。需要在 helm install 之后再修改 deployment 来实现。

实现案例

avatar

EFK

ELK(Elasticsearch , Logstash, Kibana)的日志收集方案,这种方案适用于专门收集微服务的日志。除了ELK ,还有一种日志收集的解决方案 EFK (Elasticsearch,Fluentd , Kibana),EFK 是源于 docker 生态的,这里就不展开说了,有兴趣可以查找相关资料。

参考

Kubernetes日志采集Sidecar模式介绍

深度解析Kubernetes Local Persistent Volume

Kubernetes部署ELK并使用Filebeat收集容器日志

kubernetes官方文档

轻松搞定对容器实例日志设置定期清理和回卷