type
status
date
slug
summary
tags
category
icon
password
k8s日志
日志的收集对运维工作是非常重要的,应用程序和系统日志可以帮助我们了解集群内部的运行情况,日志对于我们调试问题和监视集群情况也是非常有用的。而且大部分的应用都会有日志记录,对于传统的应用大部分都会写入到本地的日志文件之中。对于容器化应用程序来说则更简单,只需要将日志信息写入到 stdout 和 stderr 即可,容器默认情况下就会把这些日志输出到宿主机上的一个 JSON 文件之中,同样我们也可以通过
docker logs
或者 kubectl logs
来查看到对应的日志信息。下面这个示例是 Kubernetes 中的一个基本日志记录的示例,直接将数据输出到标准输出流,如下:创建完成后,可以使用
kubectl logs
命令查看日志信息当然我们也可以找到这个pod所运行的节点
可以看到这个pod运行在node1节点,下面我们进入到这个目录
通常来说容器引擎或运行时提供的功能不足以记录完整的日志信息,比如,如果容器崩溃了、Pod 被驱逐了或者节点挂掉了,我们仍然也希望访问应用程序的日志。所以,日志应该独立于节点、Pod 或容器的生命周期,这种设计方式被称为
cluster-level-logging
,即完全独立于 Kubernetes 系统,需要自己提供单独的日志后端存储、分析和查询工具。Kubernetes 集群本身不提供日志收集的解决方案,一般来说有主要的 3 种方案来做日志收集:
1、在节点上运行一个 agent 来收集日志
2、在 Pod 中包含一个 sidecar 容器来收集应用日志
3、直接在应用程序中将日志信息推送到采集后端日志收集
节点日志采集代理
在每个节点上运行一个日志收集的 agent 来采集日志数据,日志采集 agent 是一种专用工具,用于将日志数据推送到统一的后端。一般来说,这种 agent 用一个容器来运行,可以访问该节点上所有应用程序容器的日志文件所在目录。由于这种 agent 必须在每个节点上运行,所以直接使用 DaemonSet 控制器运行该应用程序即可。在节点上运行一个日志收集的 agent 这种方式是最常见的一种方法,因为它只需要在每个节点上运行一个代理程序,并不需要对节点上运行的应用程序进行更改,对应用程序没有任何侵入性,但是这种方法也仅
仅适用于收集输出到 stdout 和 stderr 的应用程序日志
。我们知道每个pod的日志都会存储在宿主机节点,我们可以使用日志采集器采集这个路径下的日志即可

sidecar容器收集日志
agent 日志采集有一个明显的问题,就是我们采集的日志都是通过输出到容器的 stdout 和 stderr 里面的信息,这些信息会在本地的容器对应目录中保留成 JSON 日志文件,所以直接在节点上运行一个 agent 就可以采集到日志。但是如果我们的应用程序的日志是
输出到容器中的某个日志文件
的话呢?这种日志数据显然只通过上面的方案是采集不到的了。对于上面这种情况我们可以直接在 Pod 中启动另外一个 sidecar 容器,直接将应用程序的日志通过这个容器重新输出到 stdout,这样是不是通过上面的节点日志收集方案又可以完成了。由于这个 sidecar 容器的主要逻辑就是将应用程序中的日志进行重定向打印,所以背后的逻辑非常简单,开销很小,而且由于输出到了 stdout 或者 stderr,所以我们也可以使用
kubectl logs
来查看日志了。
下面我们来运行一个案例,在 Pod 中将日志记录在了容器的两个本地文件之中。由于 Pod 中容器的特性,我们可以利用另外一个 sidecar 容器去获取到另外容器中的日志文件,然后将日志重定向到自己的 stdout 流中
运行成功后,我们可以通过下面的命令来查看日志的信息
这样,节点上的 agent 日志采集工具就可以自动获取这些日志信息,而不需要其它配置。但是,这种方法还有一个明显的缺陷,就是日志不仅会在原容器文件中保留下来,还会通过 stdout 输出后占用磁盘空间,这样无形中就增加了一倍磁盘空间。那么我们直接把sidecar容器换成日志采集工具不就行了么

我们这里以 fluentd sidecar 这种方式进行演示,不管你容器日志输出到哪个地方,fluentd都可以读取到。下面是 Kubernetes 官方的一个 fluentd 的配置文件示例,使用 ConfigMap 对象来保存
上面的配置文件是配置收集原文件
/var/log/1.log
和 /var/log/2.log
的日志数据,然后通过 google_cloud
这个插件将数据推送到 Stackdriver 后端去。下面是我们使用上面的配置文件在应用程序中运行一个 fluentd 的容器来读取日志数据这样的话容器的日志保存在了文件中,都没有输出到标准输出,所以执行
kubelet logs
也无法查看日志如果 Pod 崩溃,比如业务容器异常退出,那么 Sidecar 容器也无法继续读取日志。因此,在每个 Pod 内运行一个 Sidecar 进行日志采集,会带来较大的资源开销。默认情况下,每个 Kubernetes 节点可以运行最多 110 个 Pod。如果每个 Pod 都使用 Sidecar 容器采集日志,那么在一个节点上,日志采集容器的数量也会达到 110 个。相比之下,节点级别的日志采集方案,如直接在节点上运行 Filebeat 或 Fluentd 只需要一个采集容器,从而大幅减少资源占用。因此,如果采用 Sidecar 方式采集日志,日志采集工具本身需要尽可能轻量,以降低额外的资源消耗。
EFK 日志系统
Elasticsearch 是一个实时的、分布式的可扩展的搜索引擎,允许进行全文、结构化搜索,它通常用于索引和搜索大量日志数据,也可用于搜索许多不同类型的文档。Elasticsearch 通常与 Kibana 一起部署,Kibana 是 Elasticsearch 的一个功能强大的数据可视化 Dashboard,Kibana 允许你通过 web 界面来浏览 Elasticsearch 日志数据。
Fluentd是一个流行的开源数据收集器,我们将在 Kubernetes 集群节点上安装 Fluentd,通过获取容器日志文件、过滤和转换日志数据,然后将数据传递到 Elasticsearch 集群,在该集群中对其进行索引和存储。
我们先来配置启动一个可扩展的 Elasticsearch 集群,然后在 Kubernetes 集群中创建一个 Kibana 应用,最后通过 DaemonSet 来运行 Fluentd,以便它在每个 Kubernetes 工作节点上都可以运行一个 Pod。
部署 ES
在创建 Elasticsearch 集群之前,我们先创建一个命名空间,我们将在其中安装所有日志相关的资源对象
ElasticSearch 安装有最低安装要求,如果安装后 Pod 无法正常启动,请检查是否符合最低要求的配置,要求如下

这里我们要安装的 ES 集群环境信息如下所示:

这里我们使用一个 NFS 类型的 StorageClass 来做持久化存储,当然如果你是线上环境建议使用 Local PV 或者 Ceph RBD 之类的存储来持久化 Elasticsearch 的数据。此外由于 ElasticSearch 7.x 版本默认安装了 X-Pack 插件,并且部分功能免费,需要我们配置一些安全证书文件。运行容器生成证书文件
将证书从容器中拷贝到宿主机节点
删除容器
将 pcks12 中的信息分离出来,写入文件
添加证书到 Kubernetes
设置集群用户名密码
添加 ELastic 的 Helm 仓库
ElaticSearch 安装需要安装三次,分别安装 Master、Data、Client 节点,Master 节点负责集群间的管理工作;Data 节点负责存储数据;Client 节点负责代理 ElasticSearch Cluster 集群,负载均衡。搜索可用chart包
首先使用
helm pull
拉取 Chart 并解压在 Chart 目录下面创建用于 Master 节点安装配置的 values 文件
创建用于 Data 节点安装的 values 文件
创建 Client 节点的 values 文件
进入到elasticsearch目录,现在用上面的 values 文件来安装
安装 master 节点
安装 data 节点
安装 client 节点
在安装 Master 节点后 Pod 启动时候会抛出异常,就绪探针探活失败,这是个正常现象。在执行安装 Data 节点后 Master 节点 Pod 就会恢复正常。查看部署的ES集群
部署 Kibana
Elasticsearch 集群安装完成后接下来配置安装 Kibana,使用
helm pull
命令拉取 Kibana Chart 包并解压创建用于安装 Kibana 的 values 文件
部署
查看部署好的kibana
我们再部署 Kibana 的时候指定了 30601 的 NodePort 端口,所以我们可以从任意节点 IP:30601 来访问 Kibana。我们这里配置的用户名和密码都是 elastic。登录成功后进入如下所示的 Kibana 主页

部署 ECK
es部署两种方式,如果已经部署了,此步骤忽略
Elastic Cloud on Kubernetes(ECK) 是一种 Kubernetes Operator,为了方便我们管理Elastic Stack全家桶中的各种组件,例如 Elasticsearch,Kibana,APM,Beats 等。通过Operator我们可以快速部署一套Elasticsearch集群,并大大简化日常运维工作。官方也有基于 Kubernetes Operator 的应用:<a href="https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-quickstart.html">Elastic Cloud on Kubernetes (ECK)</a>,用户可使用该产品在 Kubernetes 上配置、管理和运行 Elasticsearch 集群。
ECK 不仅能自动完成所有运行和集群管理任务,还专注于简化在 Kubernetes 上使用 Elasticsearch 的完整体验。ECK 的愿景是为 Kubernetes 上的 Elastic 产品和解决方案提供 SaaS 般的体验。在 ECK 上启动的所有 Elasticsearch 集群都默认受到保护,这意味着在最初创建的那一刻便已启用加密并受到默认强密码的保护。通过 ECK 部署的所有集群都包括强大的基础(免费)级功能,例如可实现密集存储的冻结索引、Kibana Spaces、Canvas、Elastic Maps,等等,甚至可以使用 Elastic Logs 和 Elastic Infrastructure 应用监测 Kubernetes 日志和基础设施。我们可以获得在 Kubernetes 上使用 Elastic Stack 完整功能的体验。
ECK 内构建了 Elastic Local Volume,这是一个适用于 Kubernetes 的集成式存储驱动器。ECK 中还融入了很多最佳实践,例如在缩小规模之前对节点进行 drain 操作,在扩大规模的时候对分片进行再平衡,等等。确保在配置变动过程中不会丢失数据,到确保在规模调整过程中实现零中断。
接下来我们就来安装下 ECK,需要注意的是目前的 ECK 版本支持的 Kubernetes 集群版本为 1.24-1.27,我这里的k8s版本是1.27,首先需要安装 CRD
这会将 ECK 支持的 CRD 安装到 Kubernetes 集群中,接下来我们就可以安装 ECK Operator 了
ECK Operator 默认会被安装在 elastic-system 命名空间中,同样还会创建一系列 RBAC 规则,这些规则用于控制 ECK Operator 对集群的管理权限,最终的 Operator 会以 StatefulSet 的方式运行在 Kubernetes 集群中。
ECK Operator 启动成功后,我们就可以开始创建 Elasticsearch 集群了。但是需要注意的是如果你的 Kubernetes 集群没有任何具有至少 2GiB 可用内存的节点,则 pod 将陷入 Pending 状态,这是因为 Elasticsearch 集群需要至少 2GiB 内存才能正常运行。要创建 Elasticsearch 集群,现在我们只需要创建一个 Elasticsearch 资源对象就可以了,这个资源对象定义了 Elasticsearch 集群的配置,比如版本、节点数量、存储大小、内存大小等等
ES数据持久化通过使用挂载PV实现。目前实现方案有以下两种:
方案1:使用本地持久卷,优点是提供了最大的IO性能,缺点是pod与主机强绑定。如果主机出现故障,上面的pod无法调度至其他主机,直到主机恢复后上面异常的pod才能正常运行,集群扩展节点时需要手动操作配置PV。适用于数据量较大,追求集群更高的读写性能,集群规模变更不频繁的场景。
方案2:使用网络持久卷(例如ceph RBD块存储、公有云云盘),优点是如果主机出现故障,可以快速将上面的pod调度至其他主机运行,自动完成故障切换无需人工干预,还可以根据集群负载状态实现节点自动扩缩容。缺点是读写性能受网络带宽影响,速度低于本地持久卷。适用于数据量较小,追求集群服务更高的SLO,集群规模频繁变更的场景。
如果是IDC机房私有化环境,使用ECK部署ES集群时,推荐使用
local pv
或者ceph RBD块存储
方案存储ES数据,使用minIO或者ceph RGW对象方案存储ES备份数据。如果是公有云环境,可一步到位直接购买现成的ES集群服务。如果使用ECK部署ES集群时,以阿里云为例,在选择ES节点数据存储类型时,hot节点使用高效云盘,warm节点使用SSD云盘,clod节点使用ESSD云盘。ES数据备份使用OSS对象存储。本实验ES存储使用local pv,首先创建一个存储类。
provisioner 字段定义为 no-provisioner,这是因为 Local Persistent Volume 目前尚不支持 Dynamic Provisioning 动态生成 PV,所以我们需要提前手动创建 PV。volumeBindingMode 字段定义为 WaitForFirstConsumer,它是 Local Persistent Volume 里一个非常重要的特性,即:延迟绑定。延迟绑定就是在我们提交 PVC 文件时,StorageClass 为我们延迟绑定 PV 与 PVC 的对应关系。查看创建好的存储类
创建local pv资源
创建Elasticsearch,注意我们这里仅为测试使用
上面的配置文件中我们定义了一个 Elasticsearch 集群,集群名称为 demo,版本为 8.9.0,节点数量为 1,并且禁用了 mmap。默认情况下,Operator 会为 Elasticsearch 集群中的每个 Pod 创建一个容量为 1Gi 的 PVC,所以需要你集群中有默认的 StorageClass 才可以生效。当然我们也可以定义自己的卷声明模板,其中包含所需的存储容量和(可选)与持久卷关联的 Kubernetes 存储类。创建后,ECK Operator 会自动创建和管理 Kubernetes 资源,以实现 Elasticsearch 集群的理想状态。
系统会自动创建一个名为 elastic 的默认用户,其密码存储在 Kubernetes Secret 中,获取elastic用户密码
然后我们可以通过 curl 命令来测试集群是否正常
如果看到上面的信息输出证明这个 Elasticsearch 集群已经正常运行了。
当然我们还可以添加和修改原始集群规范,当然我们需要确保 Kubernetes 集群有足够的资源来适应更改,额外的存储空间、足够的内存和 CPU 资源来临时启动新的 pod 等,比如我们可以将 count 修改为 2,然后重新应用资源对象即可
我们这里使用的是local pv,创建pv
然后查看 Elasticsearch 数量变为2个
同样如果想将 Elasticsearch 按角色分组,可以使用 nodeSets 来定义,比如我们可以定义一个 master 节点组,然后将 nodeSets 的
config.node.roles
设置为 master 即可,如下所示:Elasticsearch 集群安装完成后接下来配置安装 Kibana,同样的我们只需要创建一个 Kibana 的 CRD 资源对象即可,如下所示:
在上面的配置文件中我们定义了一个 Kibana 资源对象,名称为 kibana,版本为 8.9.0,节点数量为 1,并且关联了上面创建的 Elasticsearch 集群,同样为了方便测试我们也禁用了 TLS,并将 Kibana 以 NodePort 的方式暴露出来。创建后,ECK Operator 会自动创建和管理 Kubernetes 资源,以实现 Kibana 集群的理想状态,这里会创建一个 Kibana Deployment 和一个 Kibana Service
当 Kibana 的 Pod 成功运行后,就可以从任意节点IP:30468来访问 Kibana 了。这里我们输入上面配置的用户名 elastic,密码为上面获取的 PASSWORD 的值。登录成功后进入如下所示的 Kibana 主页

部署 Fluentd
Fluentd 是一个高效的日志聚合器,是用 Ruby 编写的,并且可以很好地扩展。对于大部分企业来说,Fluentd 足够高效并且消耗的资源相对较少,另外一个工具 Fluent-bit 更轻量级,占用资源更少,但是插件相对 Fluentd 来说不够丰富,所以整体来说,Fluentd 更加成熟,使用更加广泛,所以这里我们使用 Fluentd 来作为日志收集工具。

要收集 Kubernetes 集群的日志,直接用 DasemonSet 控制器来部署 Fluentd 应用,这样,它就可以从 Kubernetes 节点上采集日志,确保在集群中的每个节点上始终运行一个 Fluentd 容器。当然可以直接使用 Helm 来进行一键安装,为了能够了解更多实现细节,我们这里还是采用手动方法来进行安装。使用官方的对于 Kubernetes 集群的安装文档:
首先,我们通过 ConfigMap 对象来指定 Fluentd 配置文件,新建
fluentd-configmap.yaml
文件,文件内容如下,注意修改es的host地址和连接密码上面配置文件中我们只配置了 docker 容器日志目录,收集到数据经过处理后发送到 elasticsearch-client:9200 服务。然后我们创建 Fluentd DaemonSet
查看部署好的fluentd
另外为了能够灵活控制哪些节点的日志可以被收集,还可以添加了一个 nodSelector 属性
如果你需要在其他节点上采集日志,则需要给对应节点打上标签,使用如下命令,我们这里给三个节点都打上标签
查看对应的 Pods 列表,检查是否部署成功
Fluentd 启动成功后,这个时候就可以发送日志到 ES 了,但是我们这里是过滤了只采集具有 l
ogging=true
标签的 Pod 日志,所以现在还没有任何数据会被采集。下面我们部署一个简单的测试应用, 新建一个pod,该 Pod 只是简单将日志信息打印到 stdout,所以正常来说 Fluentd 会收集到这个日志数据,在 Kibana 中也就可以找到对应的日志数据了,使用 kubectl 工具创建该 Pod查看创建好的pod
配置 Kibana Dashboard
Pod 创建并运行后,回到 Kibana Dashboard 页面,点击左侧最下面的 Management -> Stack Management,进入管理页面,点击左侧 Kibana 下面的索引模式,点击 创建索引模式 开始导入索引数据。在这里可以配置我们需要的 Elasticsearch 索引,前面 Fluentd 配置文件中我们采集的日志使用的是 logstash 格式,定义了一个 k8s 的前缀,所以这里只需要在文本框中输入
k8s-*
即可匹配到 Elasticsearch 集群中采集的 Kubernetes 集群日志数据
创建完成后,点击左侧导航菜单中的 Discover,并在 Data views 区域选择上面创建的 K8sLogs,然后就可以看到一些直方图和最近采集到的日志数据了

我们也可以通过其他元数据来过滤日志数据,比如您可以单击任何日志条目以查看其他元数据,如容器名称,Kubernetes 节点,命名空间等。
部署 Kafka
对于大规模集群来说,日志数据量是非常巨大的,如果直接通过 Fluentd 将日志打入 Elasticsearch,对 ES 来说压力是非常巨大的,我们可以在中间加一层消息中间件来缓解 ES 的压力,一般情况下我们会使用 Kafka,然后可以直接使用 kafka-connect-elasticsearch 这样的工具将数据直接打入 ES,也可以在加一层 Logstash 去消费 Kafka 的数据,然后通过 Logstash 把数据存入 ES,这里我们来使用 Logstash 这种模式来对日志收集进行优化。
使用 Helm 安装 Kafka 集群
使用 helm pull 拉取 Chart 并解压:
这里面我们指定使用一个 NFS StorageClass 来提供持久化存储,在 Chart 目录下面创建用于安装的 values 文件
部署kafka
安装完成后我们可以使用上面的提示来检查 Kafka 是否正常运行
用下面的命令创建一个 Kafka 的测试客户端 Pod
然后启动一个终端进入容器内部生产消息
启动另外一个终端进入容器内部消费消息
如果在消费端看到了生产的消息数据证明我们的 Kafka 已经运行成功了。
Fluentd 配置 Kafka
将 Fluentd 的日志数据输出到 Kafka 了,只需要将 Fluentd 配置中的 <match> 更改为使用 Kafka 插件即可,但是在 Fluentd 中输出到 Kafka,需要使用到 fluent-plugin-kafka 插件,所以需要我们自定义下 Docker 镜像,最简单的做法就是在上面 Fluentd 镜像的基础上新增 kafka 插件即可,Dockerfile 文件如下所示:
部署 Logstash
虽然数据从 Kafka 到 Elasticsearch 的方式多种多样,比如可以使用 Kafka Connect Elasticsearch Connector 来实现,我们这里还是采用更加流行的 Logstash 方案,上面我们已经将日志从 Fluentd 采集输出到 Kafka 中去了,接下来我们使用 Logstash 来连接 Kafka 与 Elasticsearch 间的日志数据。
首先使用 helm pull 拉取 Chart 并解压:
同样在 Chart 根目录下面创建用于安装的 Values 文件,如下所示:
同样在 Chart 根目录下面创建用于安装的 Values 文件,如下所示:
其中最重要的就是通过 logstashPipeline 配置 logstash 数据流的处理配置,通过 input 指定日志源 kafka 的配置,通过 output 输出到 Elasticsearch,同样直接使用上面的 Values 文件安装 logstash 即可:
安装启动完成后可以查看 logstash 的日志:
由于我们启用了 debug 日志调试,所以我们可以在 logstash 的日志中看到我们采集的日志消息,到这里证明我们的日志数据就获取成功了。现在我们可以登录到 Kibana 可以看到有如下所示的索引数据了
- 链接:www.qianshuai.cn/article/efk
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。