构建智能化日志收集平台-EFK日志管理系统

一、日志收集

  在生产环境或者测试环境,如果某个服务或者业务组件出现问题,如何定位和排查?需要靠日志,日志是定位问题的重要手段。

1. 日志打印常见级别

  日志打印通常有四种级别,从高到底分别是:ERROR、WARN、INFO、DEBUG。日志级别中的优先级就是在系统中如果开启了某一级别的日志后,就不会打印比它级别低的日志。例如,程序如果开启了INFO级别日志,DEBUG日志就不会打印,通常在生产环境中开启INFO日志。

1)DEBUG:

  DEBU可以打印出最详细的日志信息,主要用于开发过程中打印一些运行信息。

2)INFO:

  INFO可以打印一些感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志

3)WARNING:

  WARNING 表明发生了一些暂时不影响运行的错误,会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示

4)ERROR:

  ERROR 可以打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别,这一级就是比较重要的错误了,软件的某些功能已经不能继续执行了

2. 日志收集方案

  应用程序的日志收集和监控通常是其必要的外围功能,它们有助于记录、分析性能表现及排查故障等,例如在查看pod对象的日志时使用的kubectl logs命令便是获取容器化应用日志的一种方式。然而,对于分布式部署的应用来说,类似这种逐一查看各实例相关日志的方式存在着操作繁琐且效率低下等诸多问题,再加上需要额外获取操作系统级别的多个日志源中的日志信息,其管理成本势必会进一步上升。解决此类需求的常见方案是使用集中式日志存储和管理系统,它们于各节点部署日志采集代理程序从日志源采集日志并发往中心存储管理系统,并经由单个面板进行数据可视化。事实上,对于任何基础设施或分布式系统,统一日志管理都是必不可少的基础组件。同样地,kubernetes也要实现在整个集群级别收集和聚合日志,以便用户可以从单个仪表板监控整个集群。

  在Kubernetes集群上运行多个服务和应用程序时,日志收集系统可以帮助快速分类和分析由Pod生成的大量日志数据。但是Kubernetes 集群本身不提供日志收集的解决方案,常用的架构形式之一是基于节点日志采集代理完成日志收集,如下图所示:

  该种方案是通过在每个节点上运行一个日志收集的 agent 来采集日志数据,日志采集 agent 是一种专用工具,用于将日志数据推送到统一的后端。一般来说,这种 agent 用一个容器来运行,可以访问该节点上所有应用程序容器的日志文件所在目录。由于这种 agent 必须在每个节点上运行,所以直接使用 DaemonSet 控制器运行该应用程序即可。在节点上运行一个日志收集的 agent 这种方式是最常见的一直方法,因为它只需要在每个节点上运行一个代理程序,并不需要对节点上运行的应用程序进行更改,对应用程序没有任何侵入性,但是这种方法也仅仅适用于收集输出到 stdout 和 stderr 的应用程序日志。

  常用的日志收集方案,有以下几种:

1)EFK

  Kubernetes中比较流行的日志收集解决方案是Elasticsearch、Fluentd和Kibana(EFK)技术栈,也是官方推荐的一种方案。它是将Fluentd作为节点级代理程序进行日志采集,并将之聚合存储于Elasticsearch进行日志分析,以及通过Kibana进行数据可视化,这种组合通常简称为EFK。 

  Elasticsearch是一个实时的,分布式的,可扩展的搜索引擎,它允许进行全文本和结构化搜索以及对日志进行分析。它通常用于索引和搜索大量日志数据,也可以用于搜索许多不同种类的文档。

  Elasticsearch通常与Kibana一起部署,kibana可以把Elasticsearch采集到的数据通过dashboard(仪表板)可视化展示出来。Kibana允许通过Web界面浏览Elasticsearch日志数据,也可自定义查询条件快速检索出elasticccsearch中的日志数据。

  Fluentd是一个流行的开源数据收集器,可以在 Kubernetes 集群节点上安装 Fluentd,通过获取容器日志文件、过滤和转换日志数据,然后将数据传递到 Elasticsearch 集群,在该集群中对其进行索引和存储。

2)ELK Stack

  Elasticsearch、Logstash和Kibana简称ELK

  Elasticsearch:日志存储和搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。

  Logstash:是一个完全开源的工具,可以对日志进行收集、过滤,并将其存储供以后使用(支持动态的从各种数据源搜集数据,并对数据进行过滤、分析、丰富、统一格式等操作。)。

  Kibana 也是一个开源和免费的工具,Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。

  应用程序(AppServer)–>Logstash–>ElasticSearch–>Kibana–>浏览器(Browser):Logstash收集AppServer产生的Log,并存放到ElasticSearch集群中,而Kibana则从ElasticSearch集群中查询数据生成图表,再返回给Browser。

  注:考虑到聚合端(日志处理、清洗等)负载问题和采集端传输效率,一般在日志量比较大的时候在采集端和聚合端增加队列,以用来实现日志消峰。

3)ELK+filebeat

  Filebeat(采集)—> Logstash(聚合、处理)—> ElasticSearch (存储)—>Kibana (展示)

二、组件介绍

1. Elasticsearch组件介绍

  Elasticsearch 是一个分布式的免费开源搜索和分析引擎,适用于包括文本、数字、地理空间、结构化和非结构化数据等在内的所有类型的数据。Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。Elasticsearch 以其简单的 REST 风格 API、分布式特性、速度和可扩展性而闻名,是 Elastic Stack 的核心组件;Elastic Stack 是一套适用于数据采集、扩充、存储、分析和可视化的免费开源工具。人们通常将 Elastic Stack 称为 ELK Stack(代指 Elasticsearch、Logstash 和 Kibana),目前 Elastic Stack 包括一系列丰富的轻量型数据采集代理,这些代理统称为 Beats,可用来向 Elasticsearch 发送数据。

2. Filebeat组件介绍

1)Filebeat和beat关系

  Filebeat是Beats中的一员。

  Beats是一个轻量级日志采集器,Beats家族有6个成员。早期的ELK架构中使用Logstash收集、解析日志,但是Logstash对内存、cpu、io等资源消耗比较高。相比Logstash,Beats所占系统的CPU和内存几乎可以忽略不计。

  目前Beats包含六种工具:

(1)Packetbeat:网络数据(收集网络流量数据)

(2)Metricbeat:指标(收集系统、进程和文件系统级别的CPU和内存使用情况等数据)

(3)Filebeat:日志文件(收集文件数据)

(4)Winlogbeat:windows事件日志(收集Windows事件日志数据)

(5)Auditbeat:审计数据(收集审计日志)

(6)Heartbeat:运行时间监控(收集系统运行时的数据)

2)Filebeat是什么?

  Filebeat是用于转发和收集日志数据的轻量级传送工具。Filebeat监视指定的日志文件或位置,收集日志事件,并将它们转发到Elasticsearch或 Logstash中。

  Filebeat的工作方式如下:启动Filebeat时,它将启动一个或多个输入,这些输入将在为日志数据指定的位置中查找。对于Filebeat所找到的每个日志,Filebeat都会启动收集器。每个收集器都读取单个日志以获取新内容,并将新日志数据发送到libbeat,libbeat将聚集事件,并将聚集的数据发送到为Filebeat配置的输出。

  Filebeat工作的流程图如下:

 

  Filebeat 有两个主要组件:harvester和input。
  harvester:一个harvester负责读取单个文件的内容。harvester逐行读取每个文件,并把这些内容发送到输出。每个文件启动一个harvester。
  Input:一个input负责管理harvesters,并找到所有要读取的源。如果input类型是log,则input查找驱动器上与已定义的log日志路径匹配的所有文件,并为每个文件启动一个harvester。

3)Filebeat工作原理

  在任何环境下,应用程序都有停机的可能性。 Filebeat 读取并转发日志行,如果中断,则会记住所有事件恢复联机状态时所在位置。

  Filebeat带有内部模块(auditd,Apache,Nginx,System和MySQL),可通过一个指定命令来简化通用日志格式的收集,解析和可视化。

  FileBeat 不会让管道超负荷。FileBeat 如果是向 Logstash 传输数据,当 Logstash 忙于处理数据,会通知 FileBeat 放慢读取速度。一旦拥塞得到解决,FileBeat将恢复到原来的速度并继续传播。

  Filebeat保持每个文件的状态,并经常刷新注册表文件中的磁盘状态。状态用于记住harvester正在读取的最后偏移量,并确保发送所有日志行。Filebeat将每个事件的传递状态存储在注册表文件中。所以它能保证事件至少传递一次到配置的输出,没有数据丢失。

4)传输方案

(1)output.elasticsearch

   如果希望使用 filebeat 直接向 elasticsearch 输出数据,需要配置 output.elasticsearch

output.elasticsearch:
  hosts: ["10.0.0.131:9200"]

(2)output.logstash

  如果使用filebeat向 logstash输出数据,然后由 logstash 再向elasticsearch 输出数据,需要配置 output.logstash。 logstash 和 filebeat 一起工作时,如果 logstash 忙于处理数据,会通知FileBeat放慢读取速度。一旦拥塞得到解决,FileBeat 将恢复到原来的速度并继续传播。这样,可以减少管道超负荷的情况。

output.logstash:
  hosts: ["10.0.0.131:5044"] 

(3)output.kafka

  如果使用filebeat向kafka输出数据,然后由 logstash 作为消费者拉取kafka中的日志,并再向elasticsearch 输出数据。需要配置 output.kafka

output.kafka:
  enabled: true
  hosts: ["10.0.0.131:9092"]
  topic: elfk8stest

4. logstash组件介绍

  Logstash是一个开源数据收集引擎,具有实时管道功能。Logstash可以动态地将来自不同数据源的数据统一起来,并将数据标准化到所选择的目的地。Logstash 是一个应用程序日志、事件的传输、处理、管理和搜索的平台。可以用它来统一对应用程序日志进行收集管理,提供 Web 接口用于查询和统计。

  输入:采集各种样式、大小和来源的数据

  数据往往以各种各样的形式,或分散或集中地存在于很多系统中。Logstash 支持各种输入选择 ,可以在同一时间从众多常用来源捕捉事件。能够以连续的流式传输方式,轻松地从日志、指标、Web 应用、数据存储以及各种 AWS 服务采集数据。

  过滤器:实时解析和转换数据

  数据从源传输到存储库的过程中,Logstash 过滤器能够解析各个事件,识别已命名的字段以构建结构,并将它们转换成通用格式,以便更轻松、更快速地分析和实现商业价值。

  Logstash 能够动态地转换和解析数据,不受格式或复杂度的影响:

(1)利用 Grok 从非结构化数据中派生出结构

(2)从 IP 地址破译出地理坐标

(3)将 PII 数据匿名化,完全排除敏感字段

(4)整体处理不受数据源、格式或架构的影响

  输出:选择存储,导出数据

  尽管 Elasticsearch 是首选的输出方向,能够为搜索和分析带来无限可能,但它并非唯一选择。Logstash 提供众多输出选择,可以将数据发送到要指定的地方。

1)Logstash工作原理

  Logstash 有两个必要元素:input 和 output ,一个可选元素:filter。 这三个元素,分别代表 Logstash 事件处理的三个阶段:输入 > 过滤器 > 输出

Input负责从数据源采集数据

filter 将数据修改为你指定的格式或内容。

output 将数据传输到目的地。

  在实际应用场景中,通常输入、输出、过滤器不止一个。Logstash 的这三个元素都使用插件式管理方式,可以根据应用需要,灵活的选用各阶段需要的插件,并组合使用。

2)常见的input模块

  Logstash 支持各种输入选择 ,可以在同一时间从众多常用来源捕捉事件。能够以连续的流式传输方式,可从日志、指标、Web 应用、数据存储以及各种 AWS 服务采集数据。

file:从文件系统上的文件读取

syslog:在众所周知的端口514上侦听系统日志消息,并根据RFC3164格式进行解析

redis:从redis服务器读取,使用redis通道和redis列表。 Redis经常用作集中式Logstash安装中的“代理”,它将接收来自远程Logstash“托运人”的Logstash事件排队。

beats:处理由Filebeat发送的事件

input {
      kafka {
        bootstrap_servers => "10.0.0.131:9092"
        auto_offset_reset => "latest"
        consumer_threads => 5
        decorate_events => true
        topics => ["elktest"]
      }
}

3)常见的filter模块

  过滤器是Logstash管道中的中间处理设备。可以将条件过滤器组合在一起,对事件执行操作。

grok:解析和结构任意文本。 Grok目前是Logstash中将非结构化日志数据解析为结构化和可查询的最佳方法。

mutate:对事件字段执行一般转换。可以重命名,删除,替换和修改事件中的字段。

drop:完全放弃一个事件,例如调试事件。

clone:制作一个事件的副本,可能会添加或删除字段。

geoip:添加有关IP地址的地理位置的信息

4)常见的output模块

  将事件传输到目的地

elasticsearch:将事件数据发送给 Elasticsearch(推荐模式)。

file:将事件数据写入文件或磁盘。

graphite:将事件数据发送给 graphite(一个流行的开源工具,存储和绘制指标, http://graphite.readthedocs.io/en/latest/)。

statsd:将事件数据发送到 statsd (这是一种侦听统计数据的服务,如计数器和定时器,通过UDP发送并将聚合发送到一个或多个可插入的后端服务)。

output { 
    elasticsearch { 
        hosts => ["10.0.0.131:9200"]
        index => "elkk8stest-%{+YYYY.MM.dd}"
        }
}        

5)常见的code插件

json:以JSON格式对数据进行编码或解码。

multiline:将多行文本事件(如java异常和堆栈跟踪消息)合并为单个事件。

5. fluentd组件介绍 

  fluentd是一个针对日志的收集、处理、转发系统。通过丰富的插件系统,可以收集来自于各种系统或应用的日志,转化为用户指定的格式后,转发到用户所指定的日志存储系统之中。

  fluentd 常常被拿来和Logstash比较,fluentd 是随着Docker和es一起流行起来的agent。

(1)fluentd 比 logstash 更省资源;

(2)更轻量级的 fluent-bid 对应 filebeat,作为部署在结点上的日志收集器;

(3)fluentd 有更多强大、开放的插件数量和社区。插件多,也非常灵活,规则也不复杂。

6. fluentd、filebeat、logstash对比分析

  常见的日志采集工具有Logstash、Filebeat、Fluentd、Logagent、rsyslog等等,那么它们之间有什么区别呢?在什么情况下应该用哪一种工具?

1)Logstash

  Logstash是一个开源数据收集引擎,具有实时管道功能。Logstash可以动态地将来自不同数据源的数据统一起来,并将数据标准化到所选择的目的地。

优势
  Logstash 主要的优点就是它的灵活性,主要因为它有很多插件,详细的文档以及直白的配置格式让它可以在多种场景下应用。基本上可以在网上找到很多资源,几乎可以处理任何问题。
劣势
  Logstash 致命的问题是它的性能以及资源消耗(默认的堆大小是 1GB)。它在大数据量的情况下会是个问题。其次,它目前不支持缓存,目前的典型替代方案是将 Redis 或 Kafka 作为中心缓冲池。

典型的应用场景

  因为 Logstash 自身的灵活性以及网络上丰富的资料,Logstash 适用于原型验证阶段使用,或者解析非常的复杂的时候。在不考虑服务器资源的情况下,如果服务器的性能足够好,可以为每台服务器安装 Logstash 。同时也不需要使用缓冲,因为文件自身就有缓冲的行为,而 Logstash 也会记住上次处理的位置。如果服务器性能较差,并不推荐为每个服务器安装 Logstash ,这样就需要一个轻量的日志传输工具,将数据从服务器端经由一个或多个 Logstash 中心服务器传输到 Elasticsearch。

  随着日志项目的推进,可能会因为性能或代价的问题,需要调整日志传输的方式(log shipper)。当判断 Logstash 的性能是否足够好时,重要的是对吞吐量的需求有着准确的估计,这也决定了需要为 Logstash 投入多少硬件资源。

2)Filebeat 

  作为 Beats 家族的一员,Filebeat 是一个轻量级的日志传输工具,它的存在正弥补了 Logstash 的缺点:Filebeat 作为一个轻量级的日志传输工具可以将日志推送到中心 Logstash。

  在版本 5.x 中,Elasticsearch 具有解析的能力(像 Logstash 过滤器)— Ingest。这也就意味着可以将数据直接用 Filebeat 推送到 Elasticsearch,并让 Elasticsearch 既做解析的事情,又做存储的事情。也不需要使用缓冲,因为 Filebeat 也会和 Logstash 一样记住上次读取的偏移,如果需要缓冲(例如,不希望将日志服务器的文件系统填满),可以使用 Redis/Kafka,因为 Filebeat 可以与它们进行通信。

优势
  Filebeat 只是一个二进制文件没有任何依赖。它占用资源极少,尽管它还十分年轻,正式因为它简单,所以几乎没有什么可以出错的地方,所以它的可靠性还是很高的。它也提供了很多可以调节的点,例如:它以何种方式搜索新的文件,以及当文件有一段时间没有发生变化时,何时选择关闭文件句柄。

劣势
  Filebeat 的应用范围十分有限,所以在某些场景下会碰到问题。例如,如果使用 Logstash 作为下游管道,同样会遇到性能问题。正因为如此,Filebeat 的范围在扩大。开始时,它只能将日志发送到 Logstash 和 Elasticsearch,而现在它可以将日志发送给 Kafka 和 Redis。

3)Fluentd

  Fluentd 创建的初衷主要是尽可能的使用 JSON 作为日志输出,所以传输工具及其下游的传输线不需要猜测子字符串里面各个字段的类型。这样,它为几乎所有的语言都提供库,这也意味着,可以将它插入到自定义的程序中。

优势
  和多数 Logstash 插件一样,Fluentd 插件是用 Ruby 语言开发的非常易于编写维护。所以它数量很多,几乎所有的源和目标存储都有插件(各个插件的成熟度也不太一样)。这也意味着可以用 Fluentd 来串联所有的东西。

劣势
  因为在多数应用场景下,通过 Fluentd 得到结构化的数据,它的灵活性并不好。但是仍然可以通过正则表达式,来解析非结构化的数据。尽管,性能在大多数场景下都很好,但它并不是最好的,和 syslog-ng 一样,它的缓冲只存在与输出端,单线程核心以及 Ruby GIL 实现的插件意味着它大的节点下性能是受限的,不过,它的资源消耗在大多数场景下是可以接受的。对于小的或者嵌入式的设备,可能需要看看 Fluent Bit,它和 Fluentd 的关系与 Filebeat 和 Logstash 之间的关系类似。

典型应用场景

  Fluentd 在日志的数据源和目标存储各种各样时非常合适,因为它有很多插件。而且,如果大多数数据源都是自定义的应用,所以可以发现用 fluentd 的库要比将日志库与其他传输工具结合起来要容易很多。特别是在应用是多种语言编写的时候,即使用了多种日志库,日志的行为也不太一样。

4)Logagent

  Logagent 是 Sematext 提供的传输工具,它用来将日志传输到 Logsene(一个基于 SaaS 平台的 Elasticsearch API),因为 Logsene 会暴露 Elasticsearch API,所以 Logagent 可以很容易将数据推送到 Elasticsearch。

优势
  可以获取 /var/log 下的所有信息,解析各种格式(Elasticsearch,Solr,MongoDB,Apache HTTPD等等),它可以掩盖敏感的数据信息,例如,个人验证信息(PII),出生年月日,信用卡号码,等等。它还可以基于 IP 做 GeoIP 丰富地理位置信息(例如,access logs)。同样,它轻量又快速,可以将其置入任何日志块中。在新的 2.0 版本中,它以第三方 node.js 模块化方式增加了支持对输入输出的处理插件。重要的是 Logagent 有本地缓冲,所以不像 Logstash ,在数据传输目的地不可用时会丢失日志。

劣势
  尽管 Logagent 有些比较有意思的功能(例如,接收 Heroku 或 CloudFoundry 日志),但是它并没有 Logstash 灵活。

典型应用场景

  Logagent 作为一个可以做所有事情的传输工具是值得选择的(提取、解析、缓冲和传输)。

5)logtail

  阿里云日志服务的生产者,目前在阿里集团内部机器上运行,经过3年多时间的考验,目前为阿里公有云用户提供日志收集服务。采用C++语言实现,对稳定性、资源控制、管理等下过很大的功夫,性能良好。相比于logstash、fluentd的社区支持,logtail功能较为单一,专注日志收集功能。

优势
  logtail占用机器cpu、内存资源最少,结合阿里云日志服务的E2E体验良好。
劣势
  logtail目前对特定日志类型解析的支持较弱,后续需要把这一块补起来。

6)rsyslog

  绝大多数 Linux 发布版本默认的 syslog 守护进程,rsyslog 可以做的不仅仅是将日志从 syslog socket 读取并写入 /var/log/messages 。它可以提取文件、解析、缓冲(磁盘和内存)以及将它们传输到多个目的地,包括 Elasticsearch 。可以从此处找到如何处理 Apache 以及系统日志。

优势

  rsyslog 是经测试过的最快的传输工具。如果只是将它作为一个简单的 router/shipper 使用,几乎所有的机器都会受带宽的限制,但是它非常擅长处理解析多个规则。它基于语法的模块(mmnormalize)无论规则数目如何增加,它的处理速度始终是线性增长的。这也就意味着,如果当规则在 20-30 条时,如解析 Cisco 日志时,它的性能可以大大超过基于正则式解析的 grok ,达到 100 倍(当然,这也取决于 grok 的实现以及 liblognorm 的版本)。同时它也能找到的最轻的解析器,当然这也取决于配置的缓冲。

劣势
  rsyslog 的配置工作需要更大的代价,文档难以搜索和阅读,特别是那些对术语比较陌生的开发者。

  5.x 以上的版本格式不太一样(它扩展了 syslogd 的配置格式,同时也仍然支持旧的格式),尽管新的格式可以兼容旧格式,但是新的特性(例如,Elasticsearch 的输出)只在新的配置下才有效,然后旧的插件(例如,Postgres 输出)只在旧格式下支持。尽管在配置稳定的情况下,rsyslog 是可靠的(它自身也提供多种配置方式,最终都可以获得相同的结果),它还是存在一些 bug 。

典型应用场景

  rsyslog 适合那些非常轻的应用(应用,小VM,Docker容器)。如果需要在另一个传输工具(例如,Logstash)中进行处理,可以直接通过 TCP 转发 JSON ,或者连接 Kafka/Redis 缓冲。

  rsyslog 还适合对性能有着非常严格的要求时,特别是在有多个解析规则时。那么这就值得为之投入更多的时间研究它的配置。

三、部署EFK日志收集平台

  首先配置启动一个可扩展的 Elasticsearch 集群,然后在 Kubernetes 集群中创建一个由Deployment控制器部署并确保其持续运行的Kibana 应用,最后通过 DaemonSet控制器来运行 Fluentd,以便它在每个 Kubernetes 工作节点上都可以运行一个 Pod。

  创建一个kube-logging名称空间,在这个名称空间下安装日志收工具elasticsearch、fluentd、kibana,即将EFK组件安装到该名称空间中。

[root@k8s-master1 ~]# mkdir kube-logging
[root@k8s-master1 ~]# cd kube-logging/
[root@k8s-master1 kube-logging]# vim kube-logging.yaml
You have new mail in /var/spool/mail/root
[root@k8s-master1 kube-logging]# cat kube-logging.yaml
kind: Namespace
apiVersion: v1
metadata:
  name: kube-logging
[root@k8s-master1 kube-logging]# kubectl apply -f kube-logging.yaml
namespace/kube-logging created
[root@k8s-master1 kube-logging]# kubectl get namespaces | grep kube-logging
kube-logging                  Active   12s 

1. 创建Elasticsearch 集群

  在kube-logging名称空间中,部署一个3节点的 Elasticsearch 集群。使用3个 Elasticsearch Pod 来避免高可用下多节点集群中出现的“脑裂”问题,当一个或多个节点无法与其他节点通信时会产生“脑裂”,可能会出现几个主节点。查看Elasticsearch集群脑裂问题可参考文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html#split-brain。一个关键点是设置参数discover.zen.minimum_master_nodes=N/2+1,其中N是 Elasticsearch 集群中符合主节点的节点数,比如这里3个节点,意味着N应该设置为2。这样,如果一个节点暂时与集群断开连接,则另外两个节点可以选择一个新的主节点,并且集群可以在最后一个节点尝试重新加入时继续运行,在扩展 Elasticsearch 集群时,一定要记住这个参数。

1)创建headless service服务

  创建一个headless service的Kubernetes服务,服务名称是elasticsearch,这个服务将为3个elasticsearch的Pod定义一个DNS域。headless service不具备负载均衡也没有IP。要了解有关headless service的更多信息,可参考https://kubernetes.io/docs/concepts/services-networking/service/#headless-services。

[root@k8s-master1 kube-logging]# vim elasticsearch_svc.yaml
You have new mail in /var/spool/mail/root
[root@k8s-master1 kube-logging]# cat elasticsearch_svc.yaml
kind: Service
apiVersion: v1
metadata:
  name: elasticsearch
  namespace: kube-logging
  labels:
    app: elasticsearch
spec:
  selector:
    app: elasticsearch
  clusterIP: None
  ports:
    - port: 9200
      name: rest
    - port: 9300
      name: inter-node
[root@k8s-master1 kube-logging]# kubectl apply -f elasticsearch_svc.yaml
service/elasticsearch created

  在kube-logging名称空间定义了一个名为 elasticsearch 的 Service服务,带有app=elasticsearch标签,当Elasticsearch StatefulSet 与此服务关联时,服务将返回带有标签app=elasticsearch的 Elasticsearch Pods的DNS A记录,然后设置clusterIP=None,将该服务设置成无头服务。最后,分别定义端口9200、9300,分别用于与 REST API 交互,以及用于节点间通信。

  查看elasticsearch的service是否创建成功

[root@k8s-master1 kube-logging]# kubectl get services --namespace=kube-logging
NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)             AGE
elasticsearch   ClusterIP   None         <none>        9200/TCP,9300/TCP   13s

  可以看到已经为 Pod 设置了无头服务和一个稳定的域名.elasticsearch.kube-logging.svc.cluster.local,接下来通过 StatefulSet 来创建具体的 Elasticsearch 的 Pod 应用。

2)通过statefulset创建Elasticsearch集群

  Kubernetes StatefulSet 允许为 Pod 分配一个稳定的标识和持久化存储,Elasticsearch 需要稳定的存储来保证 Pod 在重新调度或者重启后的数据依然不变,所以需要使用 StatefulSet 来管理 Pod。

稳定存储需要用到存储卷以持久化存储数据,通过PVC依赖指定的存储类动态创建PV存储卷。本实验使用nfs Provisioner使用已经配置好的 nfs 服务器,来自动创建持久卷PV。安装nfs provisioner参考文档:https://www.cnblogs.com/jiawei2527/p/16726284.html

(1)创建一个StorageClass对象

[root@k8s-master1 kube-logging]# vim storageclass.yaml
You have new mail in /var/spool/mail/root
[root@k8s-master1 kube-logging]# cat storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: do-block-storage
provisioner: example.com/nfs   #该值需要和nfs provisioner配置的PROVISIONER_NAME处的value值保持一致
[root@k8s-master1 kube-logging]# kubectl get storageclass
NAME               PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
do-block-storage   example.com/nfs   Delete          Immediate           false                  27s 

(2)通过 StatefulSet 来创建具体的 Elasticsearch 的 Pod 应用

  ElasticSearch版本,官方Docker Hub链接:https://registry.hub.docker.com/_/elasticsearch?tab=tags&page=1&ordering=last_updated

  本次实验使用elasticsearch 7.12.1版本,提前下载好镜像:

docker pull elasticsearch:7.12.1

  新建名为 elasticsearch-statefulset.yaml 的资源清单文件,首先定义了名为 es-cluster 的 StatefulSet 对象:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: es-cluster
  namespace: kube-logging
spec:
  serviceName: elasticsearch
  replicas: 3
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch 

  定义了一个名为 es-cluster 的 StatefulSet 对象,然后定义serviceName=elasticsearch和前面创建的 Service 相关联,这可以确保使用以下 DNS 地址访问 StatefulSet 中的每一个 Pod:es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local,其中[0,1,2]对应于已分配的 Pod 序号。然后指定3个副本,将 matchLabels 设置为app=elasticsearch,所以 Pod 的模板部分.spec.template.metadata.lables也必须包含app=elasticsearch标签。

  然后定义 Pod 模板部分内容:

...
    spec:
      containers:
      - name: elasticsearch
        image: elasticsearch:7.12.1
        imagePullPolicy: IfNotPresent
        resources:
            limits:
              cpu: 1000m
            requests:
              cpu: 100m
        ports:
        - containerPort: 9200
          name: rest
          protocol: TCP
        - containerPort: 9300
          name: inter-node
          protocol: TCP
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
        env:
          - name: cluster.name
            value: k8s-logs   #集群名称
          - name: node.name   #节点的名称
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: discovery.seed_hosts   #此字段用于设置在 Elasticsearch 集群中节点相互连接的发现方法
            value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"
          - name: cluster.initial_master_nodes
            value: "es-cluster-0,es-cluster-1,es-cluster-2"
          - name: ES_JAVA_OPTS
            value: "-Xms512m -Xmx512m"  #设置为-Xms512m -Xmx512m,告诉JVM使用512 MB的最小和最大堆

  定义 StatefulSet 中的 Pod,这里使用一个elasticsearch:7.12.1,然后暴露了9200和9300两个端口,注意名称要和上面定义的 Service 保持一致。然后通过 volumeMount 声明了数据持久化目录,下面再来定义 VolumeClaims。最后就是在容器中设置的一些环境变量了:

cluster.name:Elasticsearch 集群的名称,这里命名成 k8s-logs。

node.name:节点的名称,通过metadata.name来获取。这将解析为 es-cluster-[0,1,2],取决于节点的指定顺序。

discovery.seed_hosts:此字段用于设置在 Elasticsearch 集群中节点相互连接的发现方法。使用 unicastdiscovery 方式,它为集群指定了一个静态主机列表。由于之前配置的无头服务,Pod 具有唯一的 DNS 域es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local,因此相应地设置此变量。由于都在同一个 namespace 下面,所以可以将其缩短为es-cluster-[0,1,2].elasticsearch。要了解有关 Elasticsearch 发现的更多信息,请参阅 Elasticsearch 官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-discovery.html。

cluster.initial_master_nodes:将其设置为(N/2) + 1,N是群集中符合主节点的节点的数量。有3个 Elasticsearch 节点,因此将此值设置为2(向下舍入到最接近的整数)。要了解有关此参数的更多信息,请参阅官方 Elasticsearch 文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html#split-brain。

ES_JAVA_OPTS:这里设置为-Xms512m -Xmx512m,告诉JVM使用512 MB的最小和最大堆。应该根据群集的资源可用性和需求调整这些参数。要了解更多信息,请参阅设置堆大小的相关文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html。

  接下来添加关于 initContainer 的内容:

...
      initContainers:
      - name: fix-permissions
        image: busybox:1.28
        imagePullPolicy: IfNotPresent
        command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
        securityContext:
          privileged: true
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
      - name: increase-vm-max-map
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sysctl", "-w", "vm.max_map_count=262144"]
        securityContext:
          privileged: true
      - name: increase-fd-ulimit
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sh", "-c", "ulimit -n 65536"]
        securityContext:
          privileged: true 

  这里定义了几个在主应用程序之前运行的 Init 容器,这些初始容器按照定义的顺序依次执行,执行完成后才会启动主应用容器。

  第一个名为 fix-permissions 的容器用来运行 chown 命令,将 Elasticsearch 数据目录的用户和组更改为1000:1000(Elasticsearch 用户的 UID)。因为默认情况下,Kubernetes 用 root 用户挂载数据目录,这会使得 Elasticsearch 无法访问该数据目录,可以参考 Elasticsearch 生产中的一些默认注意事项相关文档说明:https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#_notes_for_production_use_and_defaults。

  第二个名为 increase-vm-max-map 的容器用来增加操作系统对mmap计数的限制,默认情况下该值可能太低,导致内存不足的错误,要了解更多关于该设置的信息,可以查看 Elasticsearch 官方文档说明:https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html。

  最后一个初始化容器是用来执行ulimit命令增加打开文件描述符的最大数量的。

  此外 Elastisearch Notes for Production Use 文档还提到了由于性能原因最好禁用 swap,当然对于 Kubernetes 集群而言,最好也是禁用 swap 分区的。

  现在已经定义了主应用容器和它之前运行的 Init Containers 来调整一些必要的系统参数,接下来可以添加数据目录的持久化相关的配置,在 StatefulSet 中,使用 volumeClaimTemplates 来定义 volume 模板即可:

...
  volumeClaimTemplates:
  - metadata:
      name: data
      labels:
        app: elasticsearch
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: do-block-storage
      resources:
        requests:
          storage: 10Gi 

  这里使用 volumeClaimTemplates 来定义持久化模板,Kubernetes 会使用它为 Pod 创建 PersistentVolume,设置访问模式为ReadWriteOnce,这意味着它只能被 mount 到单个节点上进行读写,然后最重要的是使用了一个之前已创建的名为 do-block-storage 的 StorageClass 对象。最后,指定了每个 PersistentVolume 的大小为 10GB,可以根据自己的实际需要进行调整该值。
  因此,完整的 Elasticsearch StatefulSet 资源清单文件内容如下:

[root@k8s-master1 kube-logging]# vim elasticsearch-statefulset.yaml
You have new mail in /var/spool/mail/root
[root@k8s-master1 kube-logging]# cat elasticsearch-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: es-cluster
  namespace: kube-logging
spec:
  serviceName: elasticsearch
  replicas: 3
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      containers:
      - name: elasticsearch
        image: elasticsearch:7.12.1
        imagePullPolicy: IfNotPresent
        resources:
            limits:
              cpu: 1000m
            requests:
              cpu: 100m
        ports:
        - containerPort: 9200
          name: rest
          protocol: TCP
        - containerPort: 9300
          name: inter-node
          protocol: TCP
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
        env:
          - name: cluster.name
            value: k8s-logs   #集群名称
          - name: node.name   #节点的名称
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: discovery.seed_hosts   #此字段用于设置在 Elasticsearch 集群中节点相互连接的发现方法
            value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"
          - name: cluster.initial_master_nodes
            value: "es-cluster-0,es-cluster-1,es-cluster-2"
          - name: ES_JAVA_OPTS
            value: "-Xms512m -Xmx512m"  #设置为-Xms512m -Xmx512m,告诉JVM使用512 MB的最小和最大堆
      initContainers:
      - name: fix-permissions
        image: busybox:1.28
        imagePullPolicy: IfNotPresent
        command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
        securityContext:
          privileged: true
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
      - name: increase-vm-max-map
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sysctl", "-w", "vm.max_map_count=262144"]
        securityContext:
          privileged: true
      - name: increase-fd-ulimit
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sh", "-c", "ulimit -n 65536"]
        securityContext:
          privileged: true
  volumeClaimTemplates:
  - metadata:
      name: data
      labels:
        app: elasticsearch
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: do-block-storage
      resources:
        requests:
          storage: 10Gi

  直接使用 kubectl 工具部署即可:

[root@k8s-master1 kube-logging]# kubectl apply -f elasticsearch-statefulset.yaml
statefulset.apps/es-cluster created

  添加成功后,可以看到 kube-logging 命名空间下面的所有的资源对象:

[root@k8s-master1 kube-logging]# kubectl get sts -n kube-logging
NAME         READY   AGE
es-cluster   3/3     32s
[root@k8s-master1 kube-logging]# kubectl get pods -n kube-logging
NAME           READY   STATUS    RESTARTS   AGE
es-cluster-0   1/1     Running   0          51s
es-cluster-1   1/1     Running   0          41s
es-cluster-2   1/1     Running   0          31s

3)检查 Elasticsearch 集群是否正常运行

  Pods 部署完成后,可以通过请求一个 REST API 来检查 Elasticsearch 集群是否正常运行。使用下面的命令将本地端口9200转发到 Elasticsearch 节点(如es-cluster-0)对应的端口

[root@k8s-master1 kube-logging]# kubectl port-forward es-cluster-0 9200:9200 --namespace=kube-logging
Forwarding from 127.0.0.1:9200 -> 9200
Forwarding from [::1]:9200 -> 9200

  然后,在另外的终端窗口中,执行如下请求:

[root@k8s-master1 kube-logging]#  curl http://localhost:9200/_cluster/state?pretty
{
  "cluster_name" : "k8s-logs",
  "cluster_uuid" : "-eHOXux0S4GwqNFCwq4PKQ",
  "version" : 36,
  "state_uuid" : "gnuyQkb4T16oyJ85Z8b5ew",
  "master_node" : "-VBDvYaLSGOWc7n-8rMHIQ",
  "blocks" : { },
  "nodes" : {
    "GoVCmbgXSGOcMMiuVi3xcg" : {
      "name" : "es-cluster-2",
      "ephemeral_id" : "JzO0xJuqSUqt_QSsKRv-eg",
      "transport_address" : "10.244.36.109:9300",
      "attributes" : {
        "ml.machine_memory" : "4126896128",
        "ml.max_open_jobs" : "20",
        "xpack.installed" : "true",
        "ml.max_jvm_size" : "536870912",
        "transform.node" : "true"
      }
    },
    "vBBZtGVtSeCsiJYJhUX3tg" : {
      "name" : "es-cluster-0",
      "ephemeral_id" : "vuimn6OuRqugjEN5PqIftg",
      "transport_address" : "10.244.36.105:9300",
      "attributes" : {
        "ml.machine_memory" : "4126896128",
        "ml.max_open_jobs" : "20",
        "xpack.installed" : "true",
        "ml.max_jvm_size" : "536870912",
        "transform.node" : "true"
      }
    },
    "-VBDvYaLSGOWc7n-8rMHIQ" : {
      "name" : "es-cluster-1",
      "ephemeral_id" : "rAuEIE_HT468Z3UP7vDoDQ",
      "transport_address" : "10.244.36.115:9300",
      "attributes" : {
        "ml.machine_memory" : "4126896128",
        "ml.max_open_jobs" : "20",
        "xpack.installed" : "true",
        "ml.max_jvm_size" : "536870912",
        "transform.node" : "true"
      }
    }
  },
  "metadata" : {
    "cluster_uuid" : "-eHOXux0S4GwqNFCwq4PKQ",
    "cluster_uuid_committed" : true,
    "cluster_coordination" : {
      "term" : 1,
      "last_committed_config" : [
        "-VBDvYaLSGOWc7n-8rMHIQ",
        "vBBZtGVtSeCsiJYJhUX3tg",
        "GoVCmbgXSGOcMMiuVi3xcg"
      ],
      "last_accepted_config" : [
        "-VBDvYaLSGOWc7n-8rMHIQ",
        "vBBZtGVtSeCsiJYJhUX3tg",
        "GoVCmbgXSGOcMMiuVi3xcg"
      ],
      "voting_config_exclusions" : [ ]
    },
......

  看到上面的信息就表明名为 k8s-logs 的 Elasticsearch 集群成功创建了3个节点:es-cluster-0,es-cluster-1,和es-cluster-2,当前主节点是 es-cluster-1

2. 安装可视化组件Kibana

  Kibana是Elasticsearch的数据分析及可视化平台,能够用来搜索、查看存储在Elasticsearch索引中的数据。它可以通过各种图表进行高级数据分析及展示,用户基于Web GUI可以快速创建仪表板(dashboard)实时显示Elasticsearch的查询结果。Kibana配置过程简单便捷、图形样式丰富,可借助于Elasticsearch创建柱形图、折线图、散点图、直方图、饼图和地图等数据展示接口。Kibana增强了Elasticsearch的数据分析能力,让用户能够更加智能的分析数据和展示数据。

  部署于kubernetes上的kibana一般会有集群外的客户端访问,因此需要为其配置Ingress资源,也可以使用NodePort或LoadBalancer类型的Service资源进行服务暴露。它默认通过HTTP提供服务,在使用Ingress暴露到互联网时,建议将其配置为HTTPS类型的服务。

  Kibana版本,官方docker hub链接:https://registry.hub.docker.com/_/kibana?tab=tags&page=1&ordering=last_updated

  本次实验使用elasticsearch 7.12.1版本,提前下载好镜像:

docker pull kibana:7.12.1  

  新建一个名为 kibana.yaml 的清单文件:

[root@k8s-master1 kube-logging]# vim kibana.yaml
[root@k8s-master1 kube-logging]# cat kibana.yaml
apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: kube-logging
  labels:
    app: kibana
spec:
  ports:
  - port: 5601
  selector:
    app: kibana
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: kube-logging
  labels:
    app: kibana
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      containers:
      - name: kibana
        image: kibana:7.12.1
        imagePullPolicy: IfNotPresent
        resources:
          limits:
            cpu: 1000m
          requests:
            cpu: 100m
        env:
          - name: ELASTICSEARCH_URL
            value: http://elasticsearch:9200
        ports:
        - containerPort: 5601

  上面定义了两个资源对象,一个 Service 和 Deployment,为了测试方便将 Service 设置为了 NodePort 类型,Kibana Pod 中配置都比较简单,唯一需要注意的是使用 ELASTICSEARCH_URL 这个环境变量来设置Elasticsearch 集群的端点和端口,直接使用 Kubernetes DNS 即可,此端点对应服务名称为 elasticsearch,由于是一个 headless service,所以该域将解析为3个 Elasticsearch Pod 的 IP 地址列表。

  配置完成后,直接使用 kubectl 工具创建

[root@k8s-master1 kube-logging]# kubectl apply -f kibana.yaml
service/kibana created
deployment.apps/kibana created

  创建完成后,可以查看 Kibana Pod 的运行状态

[root@k8s-master1 kube-logging]# kubectl get pods -n kube-logging -o wide
NAME                      READY   STATUS    RESTARTS   AGE   IP              NODE        NOMINATED NODE   READINESS GATES
es-cluster-0              1/1     Running   0          24m   10.244.36.105   k8s-node1   <none>           <none>
es-cluster-1              1/1     Running   0          24m   10.244.36.115   k8s-node1   <none>           <none>
es-cluster-2              1/1     Running   0          24m   10.244.36.109   k8s-node1   <none>           <none>
kibana-64b74d78d6-zgq98   1/1     Running   0          32s   10.244.36.111   k8s-node1   <none>           <none>
[root@k8s-master1 kube-logging]# kubectl get svc -n kube-logging -o wide
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE   SELECTOR
elasticsearch   ClusterIP   None            <none>        9200/TCP,9300/TCP   90m   app=elasticsearch
kibana          ClusterIP   10.109.99.136   <none>        5601/TCP            56s   app=kibana

  修改service的type类型为NodePort

[root@k8s-master1 kube-logging]# kubectl edit svc kibana -n kube-logging

  #将type: ClusterIP变成type: NodePort,保存后退出

[root@k8s-master1 kube-logging]# kubectl edit svc kibana -n kube-logging
service/kibana edited
You have new mail in /var/spool/mail/root
[root@k8s-master1 kube-logging]#
[root@k8s-master1 kube-logging]# kubectl get svc -n kube-logging -o wide
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE     SELECTOR
elasticsearch   ClusterIP   None            <none>        9200/TCP,9300/TCP   94m     app=elasticsearch
kibana          NodePort    10.109.99.136   <none>        5601:30274/TCP      4m39s   app=kibana

  如果 Pod 已经是 Running 状态了,证明应用已经部署成功了,然后可以通过 NodePort 来访问 Kibana 这个服务,在浏览器中打开http://<任意节点IP>:30274即可,如果看到如下欢迎界面证明 Kibana 已经成功部署到了 Kubernetes集群之中。

3. 安装日志采集代理fluentd

  fluentd是一个开源的数据收集器,基于C和Ruby语言开发,它目前有数百种以RubyGem形式独立存在的可选插件,用于连接多种数据源和数据输出组件等,如fluent-plugin-elasticsearch插件用于实现将采集到的数据发送给Elasticsearch。

  fluentd的可用插件列表获取地址为:https://www.fluentd.org/plugins

  运行时,fluentd从配置文件获取数据源、数据输出目标、过滤器等相关的配置信息,这些配置信息以source、match、filter、system、label和@include配置参数给出。

source:定义数据源,每个数据源都需要由其专有类型的定义

match:数据输出的目标位置,如文件或各种存储系统

filter:过滤器,即事件处理流水线,通常运行于输入和输出之间

system:系统级设置,如处理器名称等

label:用于分组过滤器即输出目标

@include:引用配置信息中某些已有的定义,以达到配置复用的目的

1)工作原理

  Fluentd 通过一组给定的数据源抓取日志数据,处理后(转换成结构化的数据格式)将它们转发给其他服务,比如 Elasticsearch、对象存储等等。Fluentd 支持超过300个日志存储和分析服务,所以在这方面是非常灵活的。主要运行步骤如下:

(1)首先 Fluentd 从多个日志源获取数据

(2)结构化并且标记这些数据

(3)然后根据匹配的标签将数据发送到多个目标服务去

2)配置

  一般来说是通过一个配置文件来告诉 Fluentd 如何采集、处理数据的,下面简单介绍下 Fluentd 的配置方法。

(1)日志源配置

  比如为了收集 Kubernetes 节点上的所有容器日志,就需要做如下的日志源配置

<source>

@id fluentd-containers.log  #表示引用该日志源的唯一标识符,该标识可用于进一步过滤和路由结构化日志数据

@type tail      #Fluentd 内置的指令,tail表示 Fluentd 从上次读取的位置通过 tail 不断获取数据,另外一个是http表示通过一个 GET 请求来收集数据

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

pos_file /var/log/fluentd-containers.log.pos   #检查点,如果 Fluentd 程序重新启动了,它将使用此文件中的位置来恢复日志数据收集

time_format %Y-%m-%dT%H:%M:%S.%NZ

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

format json

read_from_head true

</source>
(2)路由配置

  如何将日志数据发送到 Elasticsearch

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

@id elasticsearch   #目标的一个唯一标识符

@type elasticsearch  #支持的输出插件标识符,这里要输出到 Elasticsearch,所以配置成 elasticsearch,这是 Fluentd 的一个内置插件。

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

include_tag_key true

type_name fluentd

host "#{ENV['OUTPUT_HOST']}"  #定义 Elasticsearch 的地址,也可以配置认证信息, Elasticsearch 不需要认证,所以这里直接指定 host即可

port "#{ENV['OUTPUT_PORT']}"   #定义 Elasticsearch 的地址,也可以配置认证信息, Elasticsearch 不需要认证,所以这里直接指定 port 即可

logstash_format true    #Elasticsearch 服务对日志数据构建反向索引进行搜索,将 logstash_format 设置为true,Fluentd 将会以 logstash 格式来转发结构化的日志数据

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

@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>

3)安装Fluentd组件

  使用daemonset控制器部署fluentd组件,这样可以保证集群中的每个节点都可以运行同样fluentd的pod副本,这样就可以收集k8s集群中每个节点的日志,在k8s集群中,容器应用程序的输入输出日志会重定向到node节点里的json文件中,,fluentd可以tail和过滤以及把日志转换成指定的格式发送到elasticsearch集群中。除了容器日志,fluentd也可以采集kubelet、kube-proxy、docker的日志。  

  Fluentd版本,官方docker hub链接:https://registry.hub.docker.com/_/fluentd?tab=tags&page=1&ordering=last_updated

  本次实验使用fluentd:v1.9.1-debian-1.0镜像,提前下载好镜像,命令如下:

docker pull fluentd:v1.9.1-debian-1.0

(1)创建Fluentd 配置文件

  通过 ConfigMap 对象来指定 Fluentd 配置文件,新建 fluentd-configmap.yaml 清单文件如下:

[root@k8s-master1 kube-logging]# vim  fluentd-configmap.yaml
You have new mail in /var/spool/mail/root
[root@k8s-master1 kube-logging]# cat fluentd-configmap.yaml
kind: ConfigMap
apiVersion: v1
metadata:
  name: fluentd-config
  namespace: kube-logging
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
data:
  system.conf: |-
    <system>
      root_dir /tmp/fluentd-buffers/
    </system>
  containers.input.conf: |-
    <source>
      @id fluentd-containers.log
      @type tail
      path /var/log/containers/*.log
      pos_file /var/log/es-containers.log.pos
      time_format %Y-%m-%dT%H:%M:%S.%NZ
      localtime
      tag raw.kubernetes.*
      format json
      read_from_head true
    </source>
    # Detect exceptions in the log output and forward them as one log entry.
    <match raw.kubernetes.**>
      @id raw.kubernetes
      @type detect_exceptions
      remove_tag_prefix raw
      message log
      stream stream
      multiline_flush_interval 5
      max_bytes 500000
      max_lines 1000
    </match>
  system.input.conf: |-
    # Logs from systemd-journal for interesting services.
    <source>
      @id journald-docker
      @type systemd
      filters [{ "_SYSTEMD_UNIT": "docker.service" }]
      <storage>
        @type local
        persistent true
      </storage>
      read_from_head true
      tag docker
    </source>
    <source>
      @id journald-kubelet
      @type systemd
      filters [{ "_SYSTEMD_UNIT": "kubelet.service" }]
      <storage>
        @type local
        persistent true
      </storage>
      read_from_head true
      tag kubelet
    </source>
  forward.input.conf: |-
    # Takes the messages sent over TCP
    <source>
      @type forward
    </source>
  output.conf: |-
    # Enriches records with Kubernetes metadata
    <filter kubernetes.**>
      @type kubernetes_metadata
    </filter>
    <match **>
      @id elasticsearch
      @type elasticsearch
      @log_level info
      include_tag_key true
      host elasticsearch
      port 9200
      logstash_format true
      request_timeout    30s
      <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 2M
        queue_limit_length 8
        overflow_action block
      </buffer>
    </match>

  上面配置文件中配置了 docker 容器日志目录以及 docker、kubelet 应用的日志的收集,收集到数据经过处理后发送到 elasticsearch:9200 服务。

(2)创建fluentd组件的daemonset资源

  新建一个 fluentd-daemonset.yaml 的清单文件如下:

[root@k8s-master1 kube-logging]# vim fluentd.yaml
You have new mail in /var/spool/mail/root
[root@k8s-master1 kube-logging]# cat fluentd.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd
  namespace: kube-logging
  labels:
    app: fluentd
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluentd
  labels:
    app: fluentd
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - namespaces
  verbs:
  - get
  - list
  - watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: fluentd
roleRef:
  kind: ClusterRole
  name: fluentd
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: fluentd
  namespace: kube-logging
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-logging
  labels:
    app: fluentd
spec:
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      serviceAccount: fluentd
      serviceAccountName: fluentd
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd
        image: fluentd:v1.9.1-debian-1.0
        imagePullPolicy: IfNotPresent
        env:
          - name:  FLUENT_ELASTICSEARCH_HOST
            value: "elasticsearch.kube-logging.svc.cluster.local"
          - name:  FLUENT_ELASTICSEARCH_PORT
            value: "9200"
          - name: FLUENT_ELASTICSEARCH_SCHEME
            value: "http"
          - name: FLUENTD_SYSTEMD_CONF
            value: disable
        resources:
          limits:
            memory: 512Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
        - name: config-volume
          mountPath: /etc/fluent/config.d
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: config-volume
        configMap:
          name: fluentd-config

  上面创建的 fluentd-config 这个 ConfigMap 对象通过 volumes 挂载到了 Fluentd 容器中。

  分别创建上面的 ConfigMap 对象和 DaemonSet:

[root@k8s-master1 kube-logging]# kubectl apply -f fluentd-configmap.yaml
configmap/fluentd-config created
[root@k8s-master1 kube-logging]# kubectl apply -f fluentd.yaml
serviceaccount/fluentd created
clusterrole.rbac.authorization.k8s.io/fluentd created
clusterrolebinding.rbac.authorization.k8s.io/fluentd created
daemonset.apps/fluentd created
[root@k8s-master1 kube-logging]# kubectl get pods -n kube-logging
NAME                      READY   STATUS    RESTARTS   AGE
es-cluster-0              1/1     Running   0          3h
es-cluster-1              1/1     Running   0          3h
es-cluster-2              1/1     Running   0          179m
fluentd-428rl             1/1     Running   0          46s
fluentd-ss8zw             1/1     Running   0          46s
fluentd-vmxpp             1/1     Running   0          46s
kibana-64b74d78d6-fs6jq   1/1     Running   0          173m

  确认fluentd相关的各pod对象正常运行之后,即可到Elasticsearch集群中查看其收集并存储的日志索引即数据信息。Fluentd 启动成功后,可以前往 Kibana 的 Dashboard 页面中,点击左侧的Discover,可以看到如下配置页面:

  在这里可以配置需要的 Elasticsearch 索引,前面 Fluentd 配置文件中我们采集的日志使用的是 logstash 格式,这里只需要在文本框中输入logstash-*即可匹配到 Elasticsearch 集群中的所有日志数据,然后点击下一步,进入以下页面:

  点击next step,出现下面界面,在该页面中配置使用哪个字段按时间过滤日志数据,在下拉列表中,选择@timestamp字段

  点击Create index pattern,出现如下界面:

  创建完成后,点击左侧导航菜单中的Discover,然后就可以看到一些直方图和最近采集到的日志数据了:

4)测试

  部署计数器应用到集群中,在 Kibana 中来查找该日志数据。新建 counter.yaml 文件,文件内容如下

[root@k8s-master1 kube-logging]# vim counter.yaml
You have new mail in /var/spool/mail/root
[root@k8s-master1 kube-logging]# cat counter.yaml
apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox:1.28
    imagePullPolicy: IfNotPresent
    args: [/bin/sh, -c,'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']

  该 Pod 只是简单将日志信息打印到 stdout,所以正常来说 Fluentd 会收集到这个日志数据,在 Kibana 中也就可以找到对应的日志数据了,使用 kubectl 工具创建该 Pod

[root@k8s-master1 kube-logging]# kubectl apply -f counter.yaml
pod/counter created

  Pod 创建并运行后,回到 Kibana Dashboard 页面,在上面的Discover页面搜索栏中输入kubernetes.pod_name:counter,就可以过滤 Pod 名为 counter 的日志数据:

注: Kibana查询语言KQL官方地址:https://www.elastic.co/guide/en/kibana/7.2/kuery-query.html

  至此,在 Kubernetes 集群上成功部署了 EFK ,要了解如何使用 Kibana 进行日志数据分析,可以参考 Kibana 用户指南文档:https://www.elastic.co/guide/en/kibana/current/index.html。

posted @ 2022-12-24 20:24  出水芙蓉·薇薇  阅读(1547)  评论(0编辑  收藏  举报