Kubernets:容器日志收集方案

Kubernetes 日志系统建设难点 

典型的 Kubernetes 架构

Kubernetes 上的日志方案相比我们之前基于物理机、虚拟机场景的日志方案有很大不同,例如:

  1. 日志的形式变得更加复杂,不仅有物理机/虚拟机上的日志,还有容器的标准输出、容器内的文件、容器事件、Kubernetes 事件等等信息需要采集;
  2. 环境的动态性变强,在 Kubernetes 中,机器的宕机、下线、上线、Pod销毁、扩容/缩容等都是常态,这种情况下日志的存在是瞬时的(例如如果 Pod 销毁后该 Pod 日志就不可见了),所以日志数据必须实时采集到服务端。同时还需要保证日志的采集能够适应这种动态性极强的场景
  3. 日志的种类变多,上图是一个典型的 Kubernetes 架构,一个请求从客户端需要经过 CDN、Ingress、Service Mesh、Pod 等多个组件,涉及多种基础设施,其中的日志种类增加了很多,例如 K8s 各种系统组件日志、审计日志、ServiceMesh 日志、Ingress 等;
  4. 业务架构变化,现在越来越多的公司开始在 Kubernetes 上落地微服务架构,在微服务体系中,服务的开发更加复杂,服务之间的依赖以及服务底层产品的依赖越来越多,这时的问题排查将更加复杂,如果关联各个维度的日志将是一个困难的问题;
  5. 日志方案集成困难,通常我们都会在 Kubernetes 上搭建一套 CICD 系统,这套 CICD 系统需要尽可能的自动化的完成业务的集成和部署,其中日志的采集、存储、清洗等也需要集成到这套系统中,并和 K8s 的声明式部署方式尽可能一致。而现有的日志系统通常都是较独立的系统,集成到 CICD 中代价极大;
  6. 日志规模问题,通常在系统初期的时候我们会选择自建开源的日志系统,这种方式在测试验证阶段或公司发展初期是没有什么问题的,但当业务逐渐增长,日志量增长到一定规模时,自建的开源系统很多时候都会遇到各种各样的问题,例如租户隔离、查询延迟、数据可靠性、系统可用性等。日志系统虽不是 IT 中最核心的路径,但一旦关键时刻出现这些问题都将是非常可怕的影响,例如大促的时候出现紧急问题,排查时多个工程师并发查询把日志系统打爆,导致故障恢复时间变长,大促收到影响。

K8s 日志系统设计和实践

日志需求分解与功能设计

公司内各个不同角色对于日志的相关需求,总结起来有以下几点:

  1. 支持各种日志格式、数据源的采集,包括非K8s
  2. 能够快速的查找/定位问题日志
  3. 能够将各种格式的半结构化/非结构化日志格式化,并支持快速的统计分析、可视化
  4. 支持通过日志进行实时计算并获得一些业务指标,并支持基于业务指标实时的告警(其实本质就是APM)
  5. 支持对于超大规模的日志进行各种维度的关联分析,可接受一定时间的延迟
  6. 能够便捷的对接各种外部系统或支持自定义的获取数据,例如对接第三方审计系统
  7. 能够基于日志以及相关的时序信息,实现智能的告警、预测、根因分析等,并能够支持自定义的离线训练方式以获得更好的效果
,日志平台上必须具备的功能功能模

为满足上述这些功能需求,日志平台上必须具备的功能功能模块有:

  1. 全方位日志采集,支持DaemonSet、Sidecar各种采集方式以应对不同的采集需求,同时支持Web、移动端、IoT、物理机/虚拟机各种数据源的采集;
  2. 日志实时通道,这个是为了对接上下游所必备的功能,保证日志能够被多种系统所便捷的使用;
  3. 数据清洗(ETL: Extract,Transform,Load),对各种格式的日志进行清洗,支持过滤、富化、转换、补漏、分裂、聚合等;
  4. 日志展现与搜索,这是所有日志平台必须具备的功能,能够根据关键词快速的定位到日志并查看日志上下文,看似简单的功能却最难做好;
  5. 实时分析,搜索只能完成一些定位到问题,而分析统计功能可以帮助快速分析问题的根因,同时可以用于快速的计算一些业务指标;
  6. 流计算,通常我们都会使用流计算框架(Flink、Storm、Spark Stream等)来计算一些实时的指标或对数据进行一些自定义的清洗等;
  7. 离线分析,运营、安全相关的需求都需要对大量的历史日志进行各种维度的关联计算,目前只有T+1的离线分析引擎能够完成;
  8. 机器学习框架,能够便捷、快速的将历史的日志对接到机器学习框架进行离线训练,并将训练后的结果加载到线上实时的算法库中。

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.hostnamebeat.name即pod的名称
  • source表示filebeat容器中的日志目录

我们可以通过人为得使index = service name,这样就可以方便的收集和查看每个service的日志。

问题记录

如果index: "filebeat-docker-test"没有生效,需要参考filebeat的配置文档,对filebeat的配置进一步优化。

参考链接:

发布了420 篇原创文章 · 获赞 1378 · 访问量 274万+

猜你喜欢

转载自blog.csdn.net/fly910905/article/details/104858238