kubernetes上的日志收集和日志清理
kubernetes上的日志收集和日志清理
在日益成熟的微服务架构中,可以成功地将微服务迁移到 kubernetes上,同时日志收集和日志清理成为了必须解决地问题。
日志收集工具ELK
ELK分别指Elasticsearch , Logstash, Kibana三个开源软件。同时还有一个FileBeat,它是一个轻量级的日志收集处理工具(Agent),Filebeat占用资源少,适合于在各个服务器上搜集日志后传输给Logstash 。以及一个 Kibana 插件 logtrail 用于即时查看日志。
-
Elasticsearch :Elasticsearch 是一个开源分布式搜索引擎,提供搜集、分析、存储数据 地功能,目前Github都是使用它作为检索数据库。
-
Logstash:Logstash 是一个较为重量的日志的搜集、分析、过滤日志的工具,能够处理收集来的数据流。
-
Kibana:Kibana 为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面 。
-
Filebeat:Filebeat是一个轻量的日志搜集工具,能够搜集文件日志并传输到 Elasticsearch 或 Logstash 。
-
logtrail:logtrail 是一个用于 Kibana 上即时查看日志的插件。
对于 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),将日志收集起来,并传输到日志处理的集群中。
日志清理
日志的定时清理,主要思路是每天将前一天的日志文件打包成压缩文件,然后每次都定时删除 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 来实现。
实现案例
EFK
ELK(Elasticsearch , Logstash, Kibana)的日志收集方案,这种方案适用于专门收集微服务的日志。除了ELK ,还有一种日志收集的解决方案 EFK (Elasticsearch,Fluentd , Kibana),EFK 是源于 docker 生态的,这里就不展开说了,有兴趣可以查找相关资料。
参考
深度解析Kubernetes Local Persistent Volume