Kubernetes 日志系统建设难点
典型的 Kubernetes 架构
Kubernetes 上的日志方案相比我们之前基于物理机、虚拟机场景的日志方案有很大不同,例如:
- 日志的形式变得更加复杂,不仅有物理机/虚拟机上的日志,还有容器的标准输出、容器内的文件、容器事件、Kubernetes 事件等等信息需要采集;
- 环境的动态性变强,在 Kubernetes 中,机器的宕机、下线、上线、Pod销毁、扩容/缩容等都是常态,这种情况下日志的存在是瞬时的(例如如果 Pod 销毁后该 Pod 日志就不可见了),所以日志数据必须实时采集到服务端。同时还需要保证日志的采集能够适应这种动态性极强的场景;
- 日志的种类变多,上图是一个典型的 Kubernetes 架构,一个请求从客户端需要经过 CDN、Ingress、Service Mesh、Pod 等多个组件,涉及多种基础设施,其中的日志种类增加了很多,例如 K8s 各种系统组件日志、审计日志、ServiceMesh 日志、Ingress 等;
- 业务架构变化,现在越来越多的公司开始在 Kubernetes 上落地微服务架构,在微服务体系中,服务的开发更加复杂,服务之间的依赖以及服务底层产品的依赖越来越多,这时的问题排查将更加复杂,如果关联各个维度的日志将是一个困难的问题;
- 日志方案集成困难,通常我们都会在 Kubernetes 上搭建一套 CICD 系统,这套 CICD 系统需要尽可能的自动化的完成业务的集成和部署,其中日志的采集、存储、清洗等也需要集成到这套系统中,并和 K8s 的声明式部署方式尽可能一致。而现有的日志系统通常都是较独立的系统,集成到 CICD 中代价极大;
- 日志规模问题,通常在系统初期的时候我们会选择自建开源的日志系统,这种方式在测试验证阶段或公司发展初期是没有什么问题的,但当业务逐渐增长,日志量增长到一定规模时,自建的开源系统很多时候都会遇到各种各样的问题,例如租户隔离、查询延迟、数据可靠性、系统可用性等。日志系统虽不是 IT 中最核心的路径,但一旦关键时刻出现这些问题都将是非常可怕的影响,例如大促的时候出现紧急问题,排查时多个工程师并发查询把日志系统打爆,导致故障恢复时间变长,大促收到影响。
K8s 日志系统设计和实践
日志需求分解与功能设计
公司内各个不同角色对于日志的相关需求,总结起来有以下几点:
- 支持各种日志格式、数据源的采集,包括非K8s
- 能够快速的查找/定位问题日志
- 能够将各种格式的半结构化/非结构化日志格式化,并支持快速的统计分析、可视化
- 支持通过日志进行实时计算并获得一些业务指标,并支持基于业务指标实时的告警(其实本质就是APM)
- 支持对于超大规模的日志进行各种维度的关联分析,可接受一定时间的延迟
- 能够便捷的对接各种外部系统或支持自定义的获取数据,例如对接第三方审计系统
- 能够基于日志以及相关的时序信息,实现智能的告警、预测、根因分析等,并能够支持自定义的离线训练方式以获得更好的效果
,日志平台上必须具备的功能功能模
为满足上述这些功能需求,日志平台上必须具备的功能功能模块有:
- 全方位日志采集,支持DaemonSet、Sidecar各种采集方式以应对不同的采集需求,同时支持Web、移动端、IoT、物理机/虚拟机各种数据源的采集;
- 日志实时通道,这个是为了对接上下游所必备的功能,保证日志能够被多种系统所便捷的使用;
- 数据清洗(ETL: Extract,Transform,Load),对各种格式的日志进行清洗,支持过滤、富化、转换、补漏、分裂、聚合等;
- 日志展现与搜索,这是所有日志平台必须具备的功能,能够根据关键词快速的定位到日志并查看日志上下文,看似简单的功能却最难做好;
- 实时分析,搜索只能完成一些定位到问题,而分析统计功能可以帮助快速分析问题的根因,同时可以用于快速的计算一些业务指标;
- 流计算,通常我们都会使用流计算框架(Flink、Storm、Spark Stream等)来计算一些实时的指标或对数据进行一些自定义的清洗等;
- 离线分析,运营、安全相关的需求都需要对大量的历史日志进行各种维度的关联计算,目前只有T+1的离线分析引擎能够完成;
- 机器学习框架,能够便捷、快速的将历史的日志对接到机器学习框架进行离线训练,并将训练后的结果加载到线上实时的算法库中。
Kubernetes 日志采集难点
在 Kubernetes 中,日志采集相比传统虚拟机、物理机方式要复杂很多,最根本的原因是 Kubernetes 把底层异常屏蔽,提供更加细粒度的资源调度,向上提供稳定、动态的环境。因此日志采集面对的是更加丰富、动态的环境,需要考虑的点也更加的多。
例如:
- 对于运行时间很短的 Job 类应用,从启动到停止只有几秒的时间,如何保证日志采集的实时性能够跟上而且数据不丢?
- K8s 一般推荐使用大规格节点,每个节点可以运行 10-100+ 的容器,如何在资源消耗尽可能低的情况下采集 100+ 的容器?
- 在 K8s 中,应用都以 yaml 的方式部署,而日志采集还是以手工的配置文件形式为主,如何能够让日志采集以 K8s 的方式进行部署?
Kubernetes | 传统方式 | |
日志种类 | 文件、stdout、宿主机文件、journal | 文件、journal |
日志源 | 业务容器、系统组件、宿主机 | 业务、宿主机 |
采集方式 | Agent(Sidecar、DaemonSet)、直写(DockerEngine、业务) | Agent、直写 |
单机应用数 | 10-100 | 1-10 |
应用动态性 | 高 | 低 |
节点动态性 | 高 | 低 |
采集部署方式 | 手动、Yaml | 手动、自定义 |
采集方式:主动 or 被动
日志的采集方式分为被动采集和主动推送两种,在 K8s 中,被动采集一般分为 Sidecar 和 DaemonSet 两种方式,主动推送有 DockerEngine 推送和业务直写两种方式。
- DockerEngine 本身具有 LogDriver 功能,可通过配置不同的 LogDriver 将容器的 stdout 通过 DockerEngine 写入到远端存储,以此达到日志采集的目的。这种方式的可定制化、灵活性、资源隔离性都很低,一般不建议在生产环境中使用;
- 业务直写是在应用中集成日志采集的 SDK,通过 SDK 直接将日志发送到服务端。这种方式省去了落盘采集的逻辑,也不需要额外部署 Agent,对于系统的资源消耗最低,但由于业务和日志 SDK 强绑定,整体灵活性很低,一般只有日志量极大的场景中使用;
- DaemonSet 方式在每个 node 节点上只运行一个日志 agent,采集这个节点上所有的日志。DaemonSet 相对资源占用要小很多,但扩展性、租户隔离性受限,比较适用于功能单一或业务不是很多的集群;
- Sidecar 方式为每个 POD 单独部署日志 agent,这个 agent 只负责一个业务应用的日志采集。Sidecar 相对资源占用较多,但灵活性以及多租户隔离性较强,建议大型的 K8s 集群或作为 PaaS 平台为多个业务方服务的集群使用该方式。
容器日志收集方案
总结下来:
- DockerEngine 直写一般不推荐;
- 业务直写推荐在日志量极大的场景中使用;
- DaemonSet 一般在中小型集群中使用;
- Sidecar 推荐在超大型的集群中使用。
详细的各种采集方式对比
DockerEngine | 业务直写 | DaemonSet方式 | Sidecar方式 | |
采集日志类型 | 标准输出 | 业务日志 | 标准输出+部分文件 | 文件 |
部署运维 | 低,原生支持 | 低,只需维护好配置文件即可 | 一般,需维护DaemonSet | 较高,每个需要采集日志的POD都需要部署sidecar容器 |
日志分类存储 | 无法实现 | 业务独立配置 | 一般,可通过容器/路径等映射 | 每个POD可单独配置,灵活性高 |
多租户隔离 | 弱 | 弱,日志直写会和业务逻辑竞争资源 | 一般,只能通过配置间隔离 | 强,通过容器进行隔离,可单独分配资源 |
支持集群规模 | 本地存储无限制,若使用syslog、fluentd会有单点限制 | 无限制 | 取决于配置数 | 无限制 |
资源占用 | 低,docker | |||
engine提供 | 整体最低,省去采集开销 | 较低,每个节点运行一个容器 | 较高,每个POD运行一个容器 | |
查询便捷性 | 低,只能grep原始日志 | 高,可根据业务特点进行定制 | 较高,可进行自定义的查询、统计 | 高,可根据业务特点进行定制 |
可定制性 | 低 | 高,可自由扩展 | 低 | 高,每个POD单独配置 |
耦合度 | 高,与DockerEngine强绑定,修改需要重启DockerEngine | 高,采集模块修改/升级需要重新发布业务 | 低,Agent可独立升级 | 一般,默认采集Agent升级对应Sidecar业务也会重启(有一些扩展包可以支持Sidecar热升级) |
适用场景 | 测试、POC等非生产场景 | 对性能要求极高的场景 | 日志分类明确、功能较单一的集群 | 大型、混合型、PAAS型集群 |
日志输出:Stdout or 文件
和虚拟机/物理机不同,K8s 的容器提供标准输出和文件两种方式。在容器中,标准输出将日志直接输出到 stdout 或 stderr,而 DockerEngine 接管 stdout 和 stderr 文件描述符,将日志接收后按照 DockerEngine 配置的 LogDriver 规则进行处理;日志打印到文件的方式和虚拟机/物理机基本类似,只是日志可以使用不同的存储方式,例如默认存储、EmptyDir、HostVolume、NFS 等。
虽然使用 Stdout 打印日志是 Docker 官方推荐的方式,但大家需要注意:这个推荐是基于容器只作为简单应用的场景,实际的业务场景中我们还是建议大家尽可能使用文件的方式,主要的原因有以下几点:
- Stdout 性能问题,从应用输出 stdout 到服务端,中间会经过好几个流程(例如普遍使用的 JSON LogDriver):应用 stdout -> DockerEngine -> LogDriver -> 序列化成 JSON -> 保存到文件 -> Agent 采集文件 -> 解析 JSON -> 上传服务端。整个流程相比文件的额外开销要多很多,在压测时,每秒 10 万行日志输出就会额外占用 DockerEngine 1 个 CPU 核;
- Stdout 不支持分类,即所有的输出都混在一个流中,无法像文件一样分类输出,通常一个应用中有 AccessLog、ErrorLog、InterfaceLog(调用外部接口的日志)、TraceLog 等,而这些日志的格式、用途不一,如果混在同一个流中将很难采集和分析;
- Stdout 只支持容器的主程序输出,如果是 daemon/fork 方式运行的程序将无法使用 stdout;
- 文件的 Dump 方式支持各种策略,例如同步/异步写入、缓存大小、文件轮转策略、压缩策略、清除策略等,相对更加灵活。
因此我们建议线上应用使用文件的方式输出日志,Stdout 只在功能单一的应用或一些 K8s 系统/运维组件中使用。
日志收集开源方案设计
以ELK为核心的日志平台方案:
借助于强大的开源社区,我们可以很容易基于开源软件的组合来实现这样一套日志平台,上图是一个非常典型的以ELK为核心的日志平台方案:
- 利用FileBeats、Fluentd等采集Agent实现容器上的数据统一收集。
- 为了提供更加丰富的上下游以及缓冲能力,可以使用kafka作为数据采集的接收端。
- 采集到的原始数据还需要进一步的清洗,可以使用Logstash或者Flink订阅Kafka中的数据,清洗完毕后再写入kafka中。
- 清洗后的数据可以对接ElasticSearch来做实时的查询检索、对接Flink来计算实时的指标和告警、对接Hadoop来做离线的数据分析、对接TensorFlow来做离线模型训练。
- 数据的可视化可以使用grafana、kibana等常用的可视化组件。
使用Filebeat收集Kubernetes的应用日志
Kubernetes官方提供了EFK的日志收集解决方案,但是这种方案并不适合所有的业务场景,它本身就有一些局限性,例如:
- 所有日志都必须是out前台输出,真实业务场景中无法保证所有日志都在前台输出
- 只能有一个日志输出文件,而真实业务场景中往往有多个日志输出文件
- Fluentd并不是常用的日志收集工具,我们更习惯用logstash,现使用filebeat替代
- 我们已经有自己的ELK集群且有专人维护,没有必要再在kubernetes上做一个日志收集服务
综合以上优缺点,我们选择使用Filebeat收集Kubernetes的应用日志。
filebeat日志收集架构图
该方案在扩展性、个性化、部署和后期维护方面都能做到均衡,因此选择该方案。
filebeat日志收集架构图
我们创建了自己的filebeat镜像。创建过程和使用方式见 https://github.com/rootsongjc/docker-images,您可以使用该仓库中的源码创建镜像。
filebeat日志收集测试
我们部署一个应用filebeat来收集日志的功能测试。
创建应用yaml文件filebeat-test.yaml
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: filebeat-test namespace: default spec: replicas: 3 template: metadata: labels: k8s-app: filebeat-test spec: containers: - image: harbor-001.jimmysong.io/library/filebeat:5.4.0 name: filebeat volumeMounts: - name: app-logs mountPath: /log - name: filebeat-config mountPath: /etc/filebeat/ - image: harbor-001.jimmysong.io/library/analytics-docker-test:Build_8 name : app ports: - containerPort: 80 volumeMounts: - name: app-logs mountPath: /usr/local/TalkingData/logs volumes: - name: app-logs emptyDir: {} - name: filebeat-config configMap: name: filebeat-config --- apiVersion: v1 kind: Service metadata: name: filebeat-test labels: app: filebeat-test spec: ports: - port: 80 protocol: TCP name: http selector: run: filebeat-test --- apiVersion: v1 kind: ConfigMap metadata: name: filebeat-config data: filebeat.yml: | filebeat.prospectors: - input_type: log paths: - "/log/*" - "/log/usermange/common/*" output.elasticsearch: hosts: ["172.23.5.255:9200"] username: "elastic" password: "changeme" index: "filebeat-docker-test"
filebeat-test.yaml
说明
该文件中包含了配置文件filebeat的配置文件的ConfigMap,因此不需要再定义环境变量。
当然你也可以不同ConfigMap,通过传统的传递环境变量的方式来配置filebeat。
例如对filebeat的容器进行如下配置:
containers: - image: harbor-001.jimmysong.io/library/filebeat:5.4.0 name: filebeat volumeMounts: - name: app-logs mountPath: /log env: - name: PATHS value: "/log/*" - name: ES_SERVER value: 172.23.5.255:9200 - name: INDEX value: logstash-docker - name: INPUT_TYPE value: log
目前使用这种方式会有个问题,PATHS
只能传递单个目录,如果想传递多个目录需要修改filebeat镜像的docker-entrypoint.sh
脚本,对该环境变量进行解析增加filebeat.yml文件中的PATHS列表。
推荐使用ConfigMap,这样filebeat的配置就能够更灵活。
注意事项
- 将app的
/usr/local/TalkingData/logs
目录挂载到filebeat的/log
目录下。- 该文件可以在
manifests/test/filebeat-test.yaml
找到。- 我使用了自己的私有镜像仓库,测试时请换成自己的应用镜像。
- Filebeat的环境变量的值配置请参考https://github.com/rootsongjc/docker-images
创建应用
部署Deployment
kubectl create -f filebeat-test.yaml
查看http://172.23.5.255:9200/_cat/indices
将可以看到列表有这样的indices:
green open filebeat-docker-test 7xPEwEbUQRirk8oDX36gAA 5 1 2151 0 1.6mb 841.8kb
访问Kibana的web页面,查看filebeat-2017.05.17
的索引,可以看到filebeat收集到了app日志。
Kibana页面
点开每个日志条目,可以看到以下详细字段:
filebeat收集的日志详细信息
_index
值即我们在YAML文件的configMap
中配置的index值beat.hostname
和beat.name
即pod的名称- source表示filebeat容器中的日志目录
我们可以通过人为得使index
= service name
,这样就可以方便的收集和查看每个service的日志。
问题记录
如果
index: "filebeat-docker-test"
没有生效,需要参考filebeat的配置文档,对filebeat的配置进一步优化。
参考链接:
- https://developer.aliyun.com/article/749468?spm=a2c6h.12873639.0.0.3e055f916C2XKH&groupCode=alisoftwaretech
- https://jimmysong.io/kubernetes-handbook/practice/app-log-collection.html
- https://qhh.me/2019/09/05/Kubernetes-%E5%9F%BA%E4%BA%8E-EFK-%E6%8A%80%E6%9C%AF%E6%A0%88%E7%9A%84%E6%97%A5%E5%BF%97%E6%94%B6%E9%9B%86%E5%AE%9E%E8%B7%B5/
- https://github.com/rootsongjc/kubernetes-handbook