写点什么

K8s 基于 EFK 的日志解决方案介绍

  • 2022 年 4 月 14 日
  • 本文字数:6829 字

    阅读完需:约 22 分钟

本篇文章针对一种 K8s 场景中常见的基于 ElasticSearch、Fluentd、Kibana 组合的日志解决方案进行介绍,并详细阐述了其配置使用和部署方案。

一、EFK 简介

EFK 是 ElasticSearch、Fluentd、Kibana 组合的缩写,这是 Kubernetes 场景中较为流行的开源日志解决方案,首先看一下 EFK 的能力范围。


1、Elasticsearch

ElasticSearch,是一个基于 Apache Lucene (TM)的开源搜索引擎,它提供了如下能力:

  • 分片、副本

  • 索引级别多租户

  • Resutful API、Native API Client

  • 面向文档,无需提前定制 Schema

  • 准实时搜索

  • 单文档操作具备原子性、一致性、隔离性、持久性

2、Fluentd

  • Fluentd,是 kubernets 官方推荐的日志收集方案,它提供了以下能力:

  • 可插拔的架构,社区提供丰富的插件来支持多种源与输出

  • 使用 JSON 统一日志结构

  • 较低的资源占用、高可靠性

3、Kibana

Kibana,是 ElasticSearch 数据的可视化工具,它支持:

  • Index Pattern

  • Discover

  • Visualize

  • Dashboards

  • ELK Stack 及其他插件的操作支持

二、EFK 的配置与使用

EFK 在日志解决方案中,各自有明确的分工,Fluentd 负责日志的采集,Elasticsearch 负责日志的存储与搜索,Kibana 负责日志的可视化操作。

通常情况下,K8s 中运行的应用日志会输出至标准输出流,用户通过 kubectl logs 命令获取容器的日志,从而对调试问题与应用状态监控产生帮助。

当容器发生错误终止以及重启,kubectl 会保留其日志。不过当 Pod 在工作节点中被驱逐时,Pod 中所有容器也会被驱逐,包括其对应的日志也会丢失。此外,日志的输出也会存在 logrotate 需求,也会导致对日志管理产生一定需求。

K8s 针对集群的日志管理给了几个解决方案:

  • 方法 1:使用在每个节点上运行的节点级日志记录代理。

  • 方案 2:在应用程序的 Pod 中,添加专门记录日志的 Sidecar 容器。

  • 方案 3:将日志直接从应用程序中推送到日志记录后端。

其中,方案 2、方案 3 都会涉及到对 Pod 或者应用的侵入,而 Fluentd 则是方案 1 的实践者。

Fluentd 通过以 DeamonSet 的形式运行 logging-agent(代理),在每个节点中运行一个 Pod,不需要对节点中已存在的其他应用做修改。

1、Fluentd 配置

Fluentd 的配置包含几个方面:sources 负责采集侧的配置,match 负责匹配 tag 字段将事件输出到指定位置,filter 可以链式修改事件流,output 负责事件的输出。

1)采集配置

用于指定采集数据的来源。

<source>  @id fluentd-containers.log             # 唯一标识符  @type tail                             # 内置的输入方式,从源文件中获取新的日志。  path /var/log/containers/*.log         # 挂载的服务器 Docker 容器日志地址  pos_file /var/log/es-containers.log.pos  tag raw.kubernetes.*                   # 设置日志标签  read_from_head true  <parse>                                # 多行格式化成JSON    @type multi_format                   # 使用 multi-format-parser 解析器插件    <pattern>      format json                        # 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>
复制代码

针对上面配置部分参数说明如下:

  • id:表示引用该日志源的唯一标识符,该标识可用于进一步过滤和路由结构化日志数据

  • type:Fluentd 内置的指令,tail表示 Fluentd 从上次读取的位置通过 tail 不断获取数据。也可以指定为 http 或者 forward 来接收 HTTP与 TCP 数据。

  • path:tail 类型下的特定参数,告诉 Fluentd 采集 /var/log/containers 目录下的所有日志,这是 docker 在 Kubernetes 节点上用来存储运行容器 stdout 输出日志数据的目录。

  • pos_file:检查点,如果 Fluentd 程序重新启动了,它将使用此文件中的位置来恢复日志数据收集。

  • tag:用来将日志源与目标或者过滤器匹配的自定义字符串,Fluentd 匹配源/目标标签来路由日志数据。

2)路由配置

match 指令用于查找匹配的标签。

<match **>  @id elasticsearch           # 唯一标识符  @type elasticsearch         # elasticsearch 插件  @log_level info  include_tag_key true  type_name fluentd  host "#{ENV['OUTPUT_HOST']}"  port "#{ENV['OUTPUT_PORT']}"  logstash_format true  <buffer>    @type file                # 使用文件将缓冲区块存储在磁盘上    path /var/log/fluentd-buffers/kubernetes.system.buffer    flush_mode interval    retry_type exponential_backoff    flush_thread_count 2    flush_interval 5s    retry_forever    retry_max_interval 30    chunk_limit_size "#{ENV['OUTPUT_BUFFER_CHUNK_LIMIT']}"    queue_limit_length "#{ENV['OUTPUT_BUFFER_QUEUE_LIMIT']}"    overflow_action block  </buffer></match>
复制代码

针对上面配置部分参数说明如下:

  • match:标识一个目标标签,后面是一个匹配日志源的正则表达式,我们这里想要捕获所有的日志并将它们发送给 Elasticsearch,所以需要配置成**。

  • log_level:指定要捕获的日志级别,我们这里配置成 info,表示任何该级别或者该级别以上(INFO、WARNING、ERROR)的日志都将被路由到 Elsasticsearch。

  • host/port:定义 Elasticsearch 的地址,也可以配置认证信息,我们的 Elasticsearch 不需要认证,所以这里直接指定 host 和 port 即可。

  • logstash_format:将 logstash_format 设置为 true,Fluentd 将会以 logstash 格式来转发结构化的日志数据,这有助于与 Kibana 进行适配。

  • Buffer:Fluentd 允许在目标不可用时进行缓存,比如,如果网络出现故障或者 Elasticsearch 不可用的时候。缓冲区配置也有助于降低磁盘的 IO。

3)过滤

过滤提供修改事件流的方式,可以对指定字段的值进行过滤、屏蔽、删除、添加等操作。

# 只保留具有 logging=true 标签的 Pod 日志<filter kubernetes.**>  @id filter_log                    # 唯一标识符  @type grep  <regexp>                          # 保留 key 为 true    key $.kubernetes.labels.logging    pattern ^true$  </regexp>  <exclude>                         # 排除 key 为 true    key $.kubernetes.labels.exclude    pattern ^true$  </exclude></filter>
复制代码

针对上面配置部分参数说明如下:

  • type:提供了多种过滤方式,例如 grep 过滤指定字段信息、parser 改变记录信息、geoip 增加地理位置信息等等。

  • regexp:指定过滤规则,保留符合规则的事件。

  • exclude:指定过滤规则,排除符合规则的事件。

4)输出

Fluentd 官方提供了很多种输出的插件,支持 hdfs、es、stdout、kafka 等常见的日志输出场景。此外也支持无缓存、同步缓存、异步缓存三种缓存机制。

配合路由(match)功能,可以指定输出的目标位置,例如配置 out_elasticsearch 插件:

<match my.logs>  @type elasticsearch    # 指定输出使用 out_elasticsearch 插件  host localhost         # 插件的配置信息  port 9200  logstash_format true</match>
复制代码

2、ElasticSearch 配置

ES 自身提供了发现的机制,在同一网段下,ES 节点的 cluster.name 设置为同一名称,即可自动发现组成为一个 ES 集群。当然也可以由用户手动指定 Cluster 的节点位置。

ES 的节点随着 ES 集群的规划的功能复杂度以及规模的扩大,也承担着不同的作用,即 Node roles:

  • master-eligible :有资格被选举为集群控制集群的节点。

  • data :保存数据、执行数据相关操作的节点。例如 CRUD,搜索和聚合。这些操作是 I/O,内存和 CPU 密集型的。

  • data_content :为 data 的子类,主要用于存储内容,CRUD、搜索、聚合等操作。

  • data_hot :快速读写操作的节点,适用于 SSD 硬盘的节点。

  • data_warm :数据不再定期更新,但仍有查询操作的节点,但是数据使用率较低,适用于性能较低的硬件配置。

  • data_cold :索引只读,访问频率较低。

  • ingest :用于建索引前的数据转换等操作的节点,适用于数据处理较为频繁的业务。

  • ml:用于机器学习运算的节点。

此外还有一个逻辑的 Coordnating node,即协调节点。

ES 的索引分布在不同节点的不同分片中,因而搜索的时候需要把请求分发到各个节点或者依据 _routing 的设置分发到指定节点,从各个节点中获得查询数据,返回给某个节点,并在某个节点中进行汇总、排序等操作后,形成最终的数据提供给用户。

一个简单的 master 节点的配置参考如下:

cluster.name: lakehouse-es                 # 集群名称,用来发现同一网段的其他其他节点node.name: es-1                            # 节点名称,网段内唯一node.roles: [ master ]                     # 节点角色为 masterpath.data: /apps/data/es                   # 存储集群元数据信息,data 节点存储索引数据path.repo: /apps/data/esbackuppath.logs: /apps/logs/esnetwork.host: 0.0.0.0http.port: 9200                            # Rest API 端口   transport.port: 9300                       # 节点间通信端口                              discovery.zen.ping_timeout: 30discovery.seed_hosts:                      # 启动当前节点时,发现其他节点的初始列表 ["hosts1:9300", "host2:9300", "host3:9300"]  cluster.initial_master_nodes: ["houst1"]   # 初始候选的 es master 节点
复制代码

ES 的基础配置相对简单,对于 data、ingest 节点来说,除了 node.roles 之外,没有更多的不同。

Master 节点也需要配置 path.data 信息,与 data 节点不同之处,master 节点会用来保存集群相关的元数据信息,而不是索引数据。

3、Kibana 配置

Kibana 的配置较为简单:

server.port 5601                           # 服务启动的端口server.host: "localhost"                   # 服务地址elasticsearch.hosts:                       # 指定连接的 es 集群地址,默认为 localhost  [ "<http://localhost:9200>" ]
复制代码

三、EFK 的部署

了解完 EFK 的主要配置后,下面尝试一下在 K8s 中如何把这些服务运行起来。

Elastic 官方提供了 Helm Chart 的服务,不过为了展示更多的细节,本文仍然使用配置的方式。(注:下文中没有展示整段的配置,完整配置可参考https://github.com/jkhhuse/efk-configure

首先看一下整体的服务部署架构:

Fluentd 使用 daemonSet 的方式、ES 使用 StatefulSet、Kibana 使用 Deployment 的方式部署。下面分析下这样部署的原因与方法。

1、Fluentd 部署

DaemonSet 可以保证每个节点都运行一个 Pod,集群中有节点加入,也会新增一个 Pod。Fluentd 使用 DaemonSet 的方式运行,可以保证采集到 K8s 的各个节点的日志。

# 创建 configmap fluentd 配置文件 fluentd-config# 创建 daemonSet.yaml # fluentd-config configmap 通过 volumes 挂载到 Fluentd 容器中。# 设置日志输出路径, 使用 hostpath 挂载到容器外# 设置采集节点的范围   volumeMounts:    - name: varlog      mountPath: /var/log    - name: varlibdockercontainers      mountPath: /data/docker/containers      readOnly: true    - name: config-volume      mountPath: /etc/fluent/config.d      nodeSelector:        beta.kubernetes.io/fluentd-ds-ready: "true"      tolerations:      - operator: Exists      terminationGracePeriodSeconds: 30volumes:- name: varlog  hostPath:    path: /var/log- name: varlibdockercontainers  hostPath:    path: /data/docker/containers- name: config-volume  configMap:    name: fluentd-config
复制代码

除了 ConfigMap 与 DaemonSet 之外,还需要配置 fluentd 的集群权限:

# 为 fluentd pod 创建 ServiceAcount # 创建 ClusterRole, 提供 pods、namespace 的 get/list/watch 权限# 绑定 ClusterRole 至 ServiceAcountroleRef:  kind: ClusterRole  name: fluentd  apiGroup: rbac.authorization.k8s.iosubjects:- kind: ServiceAccount  name: fluentd  namespace: default
复制代码

2、Elasticsearch 部署

考虑到大规模集群的复杂性,本文挑选了简单情况下的 ES 集群构成,即 master 与 data 构成的集群,为防止脑裂现象发生,master 节点设置为:minimum_master_nodes = (N/2)+1 ,其中 N 为集群中符合主节点条件的总数。

ES 的索引分片会涉及到同步、路由、存储、备份等持久化层面问题,所以 ES 本质上是一个 StatefulSet 的应用。

首先分别创建 master、data 节点的 headless service,用于 Rest API 以及集群内节点之间的通信:

apiVersion: v1kind: Servicemetadata:  name: elasticsearch-master / elasticsearch-data  labels:    component: elasticsearch    role: master / data  namespace: efkspec:  ports:    - port: 9200      name: rest    - port: 9300      name: transport  clusterIP: None
复制代码

随后创建 StorageClass,用于 PV 动态供给,其中 Provisioner 用于选择使用哪个卷来制备 PV,可以选择 nfs、ceph、glusterfs、local 等方式,此处选用 local 方式:

kind: StorageClassapiVersion: storage.k8s.io/v1metadata:  name: es-storageprovisioner: kubernetes.io/no-provisionervolumeBindingMode: WaitForFirstConsumer
复制代码

为了保证 PVC、PV 的自动生成、挂载与绑定,可以在 statefuleSet 的配置中增加如下配置:

volumeClaimTemplates:  - metadata:     name: data    spec:      accessModes: [ "ReadWriteOnce" ]      storageClassName: "es-storage"        # StorageClass name 保持一致      resources:        requests:          storage: 100Gi                    # 依据 provisioner 来定义存储空间
复制代码

在 containers 中的 env 中定义 elasticsearch 的配置,在 volumeMounts 中绑定自动生成的 pvc:

containers:  volumeMounts:   - name: data                              # 与 volumeClaimTemplates 定义的 name 一致    mountPath: /usr/share/elasticsearch/data  env:    ...                                     # es 的配置信息 -------- 自动生成的 pvc ------------NAME                STATUS   VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGEdata-es-cluster-0   Bound    es-pv1    1Gi        RWO            es-storage     81mdata-es-cluster-1   Bound    es-pv3    1Gi        RWO            es-storage     80mdata-es-cluster-2   Bound    es-pv2    1Gi        RWO            es-storage     78m
复制代码

不过需要注意的是由于本地 StorageClass 模式不支持 PV 的自动制备,所以本案例,PV 还是需要自动手动创建的:

apiVersion: v1kind: PersistentVolumemetadata:  name: espv1                             # 根据集群规模设置 N 个 pv  labels:    monitor: elasticsearch  namespace: efkspec:  capacity:    storage: 30Gi                         # 根据情况 data 节点需要更大的存空间  accessModes:    - ReadWriteOnce  persistentVolumeReclaimPolicy: Retain   # 回收策略设置为由管理员手工回收  storageClassName: scn  nfs:    path: /nfsdata/data1                  # 路径不重复                     server: <server-ip>
复制代码

3、Kibana 部署

Kibana 是一个前端无状态服务,且不需要在每个 Pod 都存在,所以选择使用 Deployment 来部署:

...kind: Deployment...spec:  replicas: 1                                 # 不考虑高可用,配置一个节点即可  ...        env:                                  # 配置          - name: ELASTICSEARCH_URL            value: <http://localhost:9200>  # 与 es 在一个节点,可以配置为 localhost   ...        ports:                                # 端口        - containerPort: 5601
复制代码

在 Pod 启动后,选择配置一个 nodePort service 把 Kibana 提供给外部访问:

apiVersion: v1kind: Servicemetadata:  name: kibanaspec:  selector:     app: kibana  type: NodePort    ports:    - port: 80      targetPort: 5601       nodePort: 5601
复制代码

最后,向 Fluentd 指向的日志文件中输入数据,即可在 Kibana 界面中可以搜索出对应的数据。

四、总结

本文探讨了 K8s 的日志解决方案,并对 EFK 的功能范围、配置至 K8s 部署做了较为详细的讲述。不过如何让 EFK 更好的发挥作用,及更好的在生产环境中使用,本文讲述的不多。要让 EFK 发挥出更多能量,还需要进一步思考 Fluentd 的性能、ES 集群的高可用、ingest 与 pipeline 处理及日志分析、APM、全链路追踪等等进阶功能。


用户头像

还未添加个人签名 2019.02.13 加入

还未添加个人简介

评论

发布
暂无评论
K8s 基于 EFK 的日志解决方案介绍_elasticsearch_移动云大数据_InfoQ写作平台