9.1、如何做个“有价值”的日志分析平台
基于Kubernetes这种架构的日志收集方案是一个复杂而全面的过程。需要考虑不同应用类型的日志规范,日志输出方式,应用场景,日志平台选择,架构优缺点,日志监控和性能优化等其他特殊的场景。在下面的详细分析中,将逐层讨论这些方面。
一、日志规范
在K8S 架构中,为了统一和标准化日志格式,可以使用以下几种常见的日志规范:
1、日志格式规范
- 日志格式规范
结构化日志:使用结构化日志格式,如JSON、XML或Key-Value Pari(键值对)格式,具有清晰的字段和结构,便于提取和分析重要信息。
1)、使用Json格式进行日志记录,每条日志包含以下字段:
appName:应用标识
timestamp:日志时间戳
level:日志级别
logger:日志所有的Logger名称
thread:记录日志的线程名称
message:日志消息内容
traceId:追踪日志的唯一标识符
exception:异常信息
无结构化日志:处理无结构化日志时,可以使用正则表达式等方式进行模式匹配和关键信息提取。 - 日志级别规范
1)DEBUG: 调试级别,通常用于开发和故障排查
2) INFO: 信息级别,用于记录一般日志和任务状态
3) WARN: 告警级别,用于记录潜在的非这么问题
4) ERROR: 错误级别,记录致命错误和异常情况
5) FATAL: 验证错误级别,指示应用无法正常运行 - 日志标准化规范
1)通过对每类应用的不同特性定制日志标准。
二、日志输出方式
在K8S架构中,可以使用不同的日志输出方式来收集和处理日志数据。以下是几种常用的输出方式:
- 日志到标准输出(stdout/stderr):
1) 应用程序可以直接将日志输出到标准输出或标准错误流
2)日志可以通过k8s容器引擎捕获,并存储在容器的日志文件中 - 日志到文件:
1)应用程序可以将日志输出到文件,通过挂载存储卷(Persistent Volume)或主机路径(HostPath) 实现数据的持久化
2)k8s提供了日志采集器(Filebeat、Fluentd)来监视这些日志文件并将其发送到日志平台。 - 容器日志收集器
1)可以使用专门的容器日志收集器,如Fluentd、Filebeat、Logstash等,将容器的标准输出和日志文件收集起来
2)这些日志收集器可以通过K8S的DaemonSet在每个节点上运行,从而收集整个集群的日志数据 - 特殊场景日志处理
1)单独部署Fluentd或Filebeat进行日志收集处理
三、日志平台选择
在K8S架构中,可以选择不同的日志平台来存储和分析收集的日志数据。以下是几种常见的日志平台选择:
- EFK Stack(Elasticsearch +Fluentd +Kibana)官方推荐:
1)使用Elasticsearch 作为日志存储和索引引擎
2)使用Fluentd作为日志收集器和传输工具
3)使用Kibana作为数据可视化和分析工具 - ELK Stack (Elasticsearch +Logstyash+kibana)
1)使用Elasticsearch 作为日志存储和索引引擎
2)使用Logstash作为日志收集器、解析器和传输工具
3)使用Kibana作为数据可视化和分析工具 - EFK Stask (Elasticsearch +Filebeat+kibana)
1)使用Elasticsearch 作为日志存储和索引引擎
2)使用Filebeat作为日志收集器、解析器和传输工具
3)使用Kibana作为数据可视化和分析工具 - Loki
1)Loki是一个新生且轻量的日志管理和分析平台,提供强大的服务发现机制、报告和可视化功能。
四、组件选择
日志平台中可以选择不同的组件实现日志收集和处理。
- Fluentd
1)Fluentd是一个开源的日志收集和传输工具,可与Elasearch、kafka等其他组件集成
2)它支持多种日志源和目标,并提供高度可定制的配置选项
3)稳定,支持较多的插件及场景 - Filebeat:
1)Filebeat是一个轻量级的日志文件收集器,专门用于将日志文件发送到指定位置,如Elasticsearch或Logstash。
2)它可以与K8s集成,通过监视容器日志文件并将其发送到集中式日志平台。
2)轻量,配置较简单。 - Logstash
1)Logstash是一个功能强大的日志收集、解析和传输工具,支持多种输入和输出源。
2)它可以与Elasticsearch等其他组件一起使用,实现数据的聚合、转换和传输。
五、架构优缺点
基于K8S架构的日志收集方案具有以下优点和缺点:
- 优点
1)高可扩展性:通过K8s的弹性特性,可以轻松扩展日志收集和处理组件,以适应不断增长的日志数据量。
2)高可靠性:K8s提供故障检测和自动恢复机制,确保日志收集和处理的持续性和稳定性。
3)统一管理:通过集中式的日志平台,可以对整个集群和应用程序进行集中管理和监控。
4)实时分析:通过使用实时处理引擎(如Spark Streaming、Flink),可以实现对实时日志数据的处理和分析。 - 缺点
1)复杂性:部署和管理日志收集和处理组件的复杂性较高,需要专业的知识和技能。
2)资源消耗:日志收集和处理对计算和存储资源的需求较高,可能会增加成本。
3)安全性:在设计日志收集方案时,需要考虑安全性措施,如数据加密、访问控制、身份验证等。
4)运维复杂性:在K8S架构中,日志收集方案需要与其他组件的配置和部署相互配合,增加了运维的复杂性。
六、日志监控和性能优化
为保证日志收集方案的性能和可靠性,需要注意以下几个方面:
- 监控日志平台:使用合适的监控工具(如 Prometheus 和 Grafana)监测日志平台的吞吐量、响应时间和错误率等关键指标,及时发现潜在问题。
- 自动化运维:采用自动化工具和脚本来部署、配置和监控日志收集组件,减少人工操作和降低错误风险。
- 日志清理和归档:定期清理和归档过旧的日志数据,以减少存储压力,并确保查询性能
- 查询和索引优化:合理使用查询语句和索引优化技术,提高查询性能和准确性。
- 高可用性和故障恢复:通过备份、冗余和容错技术,确保日志收集平台的高可用性,并及时处理故障。
- 预警机制:设置监控告警规则,当出现异常或超过预设阈值时,及时通知相关人员并采取相应的措施。
七、总结
基于Kubernetes的日志收集方案需要综合考虑日志规范、输出方式、日志平台选择、组件选择以及架构的优缺点。
通过合理规范日志格式、选择适当的日志输出方式,结合使用合适的日志平台和组件,可以实现高效、可靠和可扩展的日志收集和处理。
然而,也要注意应对可能的复杂性、资源优化和复杂的场景逻辑等挑战
9.2、多种日志收集方案
一、前言
集群级/应用日志通过将日志数据从容器或节点中解耦,并将其发送到集中的日志存储或处理系统,使日志数据在整个集群中可见
优势:计算容器或节点发生故障,仍然可访问日志数据进行故障排查和监控
二、应用容器标准输出流:
apiVersion: v1
kind: Pod
metadata:
name: podstdr
spec:
containers:
- name: podstdr
image: busybox
args:
[
/bin/sh,
-c,
'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done',
]
创建完成后,可以使用kubectl loghs 查看日志信息:
[root@master-1-230 9.2]# kubectl apply -f 1.yaml
pod/podstdr created
[root@master-1-230 9.2]# kubectl logs --tail 100 podstdr
0: Sun Dec 31 08:22:12 UTC 2023
1: Sun Dec 31 08:22:13 UTC 2023
2: Sun Dec 31 08:22:14 UTC 2023
3: Sun Dec 31 08:22:15 UTC 2023
4: Sun Dec 31 08:22:16 UTC 2023
5: Sun Dec 31 08:22:17 UTC 2023
三、Pod日志收集
Kubernetes 集群本身不提供日志收集的解决方案,一般主要有 4 种方案来做日志收集:
- 在节点上运行一个 agent 来收集日志;
- 在 Pod 中包含一个 sidecar 容器来收集应用日志;
- 在 Pod 中启动一个 sidecar Agent容器来收集应用日志;
- 直接在应用程序中将日志信息推送到采集后端;
3.1 节点代理方式日志采集
- DaemonSet 控制器运行该应用程序,每个节点上运行一个日志收集的 agent 来采集日志数据;
- 日志 agent 用一个容器来运行,可以访问该节点上所有应用程序容器的日志文件所在目录。
- 对应用程序没有任何侵入性,仅适用于收集输出到 stdout 和 stderr 的应用程序日志。
3.2 Sidecar方式收集容器日志
如果应用程序的日志是输出到容器中的某个日志文件呢?
在Pod中启动另外一个sidecar 容器,直接将应用程序的日志通过这个容器重新输出到stdout,即可完成日志收集。
主要逻辑:将应用程序中的日志进行重定向打印,逻辑很简单,开销很小,由于输出到stdout或者dyderr,可以使用kubectl logs 查看日志
示例:Pod中将日志记录在容器的两个本地文件中
apiVersion: v1
kind: Pod
metadata:
name: podstdr
spec:
containers:
- name: podstdr
image: busybox
args:
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
echo "$(date) INFO $i" >> /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
emptyDir: {}
利用另外一个sidecar容器取容器中的日志文件,然后将日志重定向到自己的stdout流中。
可以砂浆上面的YAML文件做如下峡谷:two-files-pod-sidecar.yaml
apiVersion: v1
kind: Pod
metadata:
name: podstdr2
spec:
containers:
- name: podstdr2
image: busybox
args:
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
echo "$(date) INFO $i" >> /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-log-1
image: busybox
args: [/bin/sh, -c, "tail -n+1 -f /var/log/1.log"]
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-log-2
image: busybox
args: [/bin/sh, -c, "tail -n+1 -f /var/log/2.log"]
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
emptyDir: {}
运行成功后,我们通过下面的命令查看日志的信息
[root@master-1-230 9.2]# kubectl logs podstdr2 count-log-1
0: Sun Dec 31 08:33:08 UTC 2023
1: Sun Dec 31 08:33:09 UTC 2023
2: Sun Dec 31 08:33:10 UTC 2023
3: Sun Dec 31 08:33:11 UTC 2023
4: Sun Dec 31 08:33:12 UTC 2023
5: Sun Dec 31 08:33:13 UTC 2023
[root@master-1-230 9.2]# kubectl logs podstdr2 count-log-2
Sun Dec 31 08:33:08 UTC 2023 INFO 0
Sun Dec 31 08:33:09 UTC 2023 INFO 1
Sun Dec 31 08:33:10 UTC 2023 INFO 2
Sun Dec 31 08:33:11 UTC 2023 INFO 3
Sun Dec 31 08:33:12 UTC 2023 INFO 4
Sun Dec 31 08:33:13 UTC 2023 INFO 5
这样前面节点上的日志采集 agent 就可以自动获取这些日志信息,而不需要其他配置。
这种方法虽然可以解决上面的问题,但是也有一个明显的缺陷,就是日志不仅会在原容器文件中保留下来,还会通过 stdout 输出后占用磁盘空间,这样无形中就增加了一倍磁盘空间。
3.3 Sidecar运行日志采集agent
主要流程:
- 部署:在同一个宿主机上部署主应用程序的容器和 Sidecar 容器。主应用程序容器负责运行应用程序本身,而 Sidecar 容器则专门负责运行日志采集 agent。
- 连接:Sidecar 容器与主应用程序容器之间建立连接,以获取需要采集的日志数据。通过共享文件系统方式实现。
- 日志采集:Sidecar 容器中运行的日志采集 agent 监听主应用程序容器的日志输出,并将日志数据收集起来。可使用特定的方式来解析不同的日志格式。
- 存储或转发:采集到的日志数据根据需求进行转发。推送到消息队列或直接推送到ES服务等。
下面是 Kubernetes 官方的一个 fluentd 的配置文件示例,使用 ConfigMap 对象来保存:
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-config
data:
fluentd.conf: |
<source>
type tail
format none
path /var/log/1.log
pos_file /var/log/1.log.pos
tag count.format1
</source>
<source>
type tail
format none
path /var/log/2.log
pos_file /var/log/2.log.pos
tag count.format2
</source>
<match **>
type google_cloud
</match>
上面的配置文件是配置收集原文件 /var/log/1.log 和 /var/log/2.log 的日志数据,然后通过 google_cloud 这个插件将数据推送到 EFK 后端。
下面是我们使用上面的配置文件在应用程序中运行一个 fluentd 的容器来读取日志数
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox
args:
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
echo "$(date) INFO $i" >> /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-agent
image: k8s.gcr.io/fluentd-gcp:1.30
env:
- name: FLUENTD_ARGS
value: -c /etc/fluentd-config/fluentd.conf
volumeMounts:
- name: varlog
mountPath: /var/log
- name: config-volume
mountPath: /etc/fluentd-config
volumes:
- name: varlog
emptyDir: {}
- name: config-volume
configMap:
name: fluentd-config
如上主要功能:容器 count-agent 就会将 count 容器中的日志进行收集然后上传。
3.4 直接从应用程序搜集日志
通过在应用程序代码中添加日志输出语句,将应用程序的关键信息记录下来。这种方式有以下几个主要步骤:
- 添加日志库:在应用程序中引入适合的日志库或框架,如Logback、Log4j、或是应用程序特定的日志库。
- 配置日志属性:根据需要,配置日志属性,如日志的级别(如DEBUG、INFO、ERROR等)、输出格式、输出位置等。
- 在应用程序中添加日志语句:在关键的代码块、重要的事件或异常处理中添加合适的日志输出语句。日志语句应该包含相关的上下文信息,如时间戳、请求参数、业务数据等,以便更好地理解日志内容。
- 输出和存储:当应用程序执行到日志语句时,日志库会根据配置将日志输出到指定的目标,如控制台、文件、数据库等。
- 日志级别控制:通过设置日志级别,可以控制哪些级别的日志会被输出和记录。
直接从应用程序收集日志的好处
- 应用程序开发者可以更精确地控制日志输出内容和格式。
- 日志与应用程序的关联更紧密,方便定位和排查问题。
- 可以根据不同的环境和需求调整日志级别和目标,以达到最佳的日志记录效果。
四、总结
- 节点代理方式:使用节点代理来采集日志。独立的代理程序,负责监控和收集该节点上运行的容器或应用程序的日志。
- Sidecar 方式收集容器日志:在容器编排平台(例如 Kubernetes)中,可以使用Sidecar 容器模式来收集主要应用程序容器的日志。这种方式中,一个专门的Sidecar 容器与主应用程序容器一起部署在同一个 Pod 中,并负责收集主应用程序容器的日志。
- Sidecar 运行日志采集 Agent:类似于上述的 Sidecar 方式,但不仅仅收集主应用程序容器的日志,还负责运行日志采集。利用容器的特性(如共享卷或网络通信)来获取主应用程序的日志,并将其发送到中央日志存储或分析系统。
- 直接从应用程序收集日志:应用程序本身负责进行日志的记录和发送。将日志数据直接发送到中央日志存储或分析系统
9.3、基于K8S架构的EFK日志平台部署管理(三节)
一、前言
Kubernetes 中比较流行的日志收集解决方案是 Elasticsearch、Fluentd 和 Kibana(EFK)技术栈,也是官方现在比较推荐的一种方案。
1.1 主要组件功能
- Elasticsearch(ES):
1)强大的搜索和查询能力:ES是一个分布式搜索和分析引擎,具有高效的搜索和查询功能。它可以处理大规模的数据,并且支持复杂的查询操作。
2)可伸缩性和高可用性:可通过增加节点来扩展存储和吞吐量,并且自动进行数据分片和副本分配,以确保高可用性和容错性。
3)实时数据分析:实时地索引和分析日志数据,可以快速地提供实时的分析结果和可视化。 - Fluentd:
1) 是一个开源的日志收集器,可以从各种数据源(如文件、应用程序日志、系统日志等)采集数据,并将其传输到指定的目标
2) 多样的插件生态系统:提供了丰富的插件生态系统,支持与各种数据源和目标的集成,如文件、数据库、消息队列等。数据的收集和导出更加灵活和可扩展。
3) 可靠性和容错性:Fluentd 具备高可靠性和容错性,通过缓冲区和重试机制,即使在网络中断或目标不可用的情况下,也能保证数据的可靠传输和持久化。 - Kibana:
1)是一个强大的数据可视化工具,可将日志数据转化为丰富的图表、仪表盘和报表。提供了各种直观易懂的可视化组件,以便快速
理解数据趋势和分析结果。
2)实时监控和警报功能:实时监控日志数据,并设置警报规则以及响应动作。让用户可以及时发现并处理异常情况,提高系统的可靠性和稳定性。
3)用户友好的界面:提供了一个直观友好的用户界面,非技术人员也能轻松地使用和定制自己的仪表盘和报表,而无需编写复杂的查询语句和代码。
1.2 EFK 组合优点
- 灵活性:EFK 技术栈中的每个组件都具有可定制和可扩展的特点,可以根据实际需求进行配置和扩展,满足不同环境和场景的需求。
- 实时性:Elasticsearch 和 Fluentd 能够实时处理和传输日志数据,日志的搜索和分析能够尽可能地接近实时。
- 可扩展性:Elasticsearch 是一个分布式存储和搜索引擎,能够水平扩展以应对大规模的日志数据。Fluentd 和 Kibana 也支持水平扩展,可以根据需要增加节点和实例,以适应日志数据量的增长。
- 可视化和分析能力:Kibana 提供了强大的可视化和分析工具,用户能够以直观的方式探索数据、构建仪表盘和生成图表,轻松进行数据分析和故障排查。
- 开源社区支持:EFK 技术栈是开源项目,有庞大的社区支持和活跃的开发者社群,提供了丰富的插件和文档资源,便于用户学习、使用和解决问题
二、ES 集群部署配置
2.1 环境准备
创建Elasticsearch 集群之前,先创建一个命名空间
[root@master-1-230 9.3]# kubectl create ns logging
namespace/logging created
2.2 安装ES 集群
添加Elastic的Helm仓库
[root@master-1-230 9.3]# helm repo add elastic https://helm.elastic.co
"elastic" has been added to your repositories
您在 /var/spool/mail/root 中有新邮件
[root@master-1-230 9.3]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "elastic" chart repository
...Successfully got an update from the "aliyun" chart repository
...Successfully got an update from the "harbor" chart repository
...Successfully got an update from the "skywalking" chart repository
...Successfully got an update from the "jetstack" chart repository
...Successfully got an update from the "gitlab" chart repository
...Successfully got an update from the "stable" chart repository
...Successfully got an update from the "bitnami" chart repository
...Successfully got an update from the "ingress-nginx" chart repository
Update Complete. ⎈Happy Helming!⎈
首先使用helm pull拉取Chart 并解压
[root@master-1-230 9.3]# helm pull elastic/elasticsearch --untar --version 7.17.3
[root@master-1-230 9.3]# cd elasticsearch/
[root@master-1-230 elasticsearch]# ll
总用量 72
-rw-r--r-- 1 root root 341 12月 31 17:34 Chart.yaml
drwxr-xr-x 14 root root 211 12月 31 17:34 examples
-rw-r--r-- 1 root root 29 12月 31 17:34 Makefile
-rw-r--r-- 1 root root 49860 12月 31 17:34 README.md
drwxr-xr-x 3 root root 297 12月 31 17:34 templates
-rw-r--r-- 1 root root 9496 12月 31 17:34 values.yaml
在 Chart 目录下面创建用于 Master 节点安装配置的 values 文件:
cat values.yaml |egrep -v "#|^$"
[root@master-1-230 elasticsearch]# cat values.yaml |egrep -v "#|^$"
---
clusterName: "elasticsearch"
nodeGroup: "master"
masterService: ""
roles:
master: "true"
ingest: "true"
data: "true"
remote_cluster_client: "true"
ml: "true"
replicas: 3
minimumMasterNodes: 2
esMajorVersion: ""
clusterDeprecationIndexing: "false"
esConfig: {}
esJvmOptions: {}
extraEnvs: []
envFrom: []
secretMounts: []
hostAliases: []
image: "docker.elastic.co/elasticsearch/elasticsearch"
imageTag: "7.17.3"
imagePullPolicy: "IfNotPresent"
podAnnotations:
{}
labels: {}
resources:
requests:
cpu: "2000m"
memory: "2Gi"
limits:
cpu: "2000m"
memory: "2Gi"
initResources:
{}
networkHost: "0.0.0.0"
volumeClaimTemplate:
storageClassName: nfs-storageclass
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 30Gi
rbac:
create: false
serviceAccountAnnotations: {}
serviceAccountName: ""
automountToken: true
podSecurityPolicy:
create: false
name: ""
spec:
privileged: true
fsGroup:
rule: RunAsAny
runAsUser:
rule: RunAsAny
seLinux:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
volumes:
- secret
- configMap
- persistentVolumeClaim
- emptyDir
persistence:
enabled: true
labels:
enabled: false
annotations: {}
extraVolumes:
[]
extraVolumeMounts:
[]
extraContainers:
[]
extraInitContainers:
[]
priorityClassName: ""
antiAffinityTopologyKey: "kubernetes.io/hostname"
antiAffinity: "hard"
nodeAffinity: {}
podManagementPolicy: "Parallel"
enableServiceLinks: true
protocol: http
httpPort: 9200
transportPort: 9300
service:
enabled: true
labels: {}
labelsHeadless: {}
type: ClusterIP
publishNotReadyAddresses: false
nodePort: ""
annotations: {}
httpPortName: http
transportPortName: transport
loadBalancerIP: ""
loadBalancerSourceRanges: []
externalTrafficPolicy: ""
updateStrategy: RollingUpdate
maxUnavailable: 1
podSecurityContext:
fsGroup: 1000
runAsUser: 1000
securityContext:
capabilities:
drop:
- ALL
runAsNonRoot: true
runAsUser: 1000
terminationGracePeriod: 120
sysctlVmMaxMapCount: 262144
readinessProbe:
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 3
timeoutSeconds: 5
clusterHealthCheckParams: "wait_for_status=green&timeout=1s"
schedulerName: ""
imagePullSecrets: []
nodeSelector: {}
tolerations: []
ingress:
enabled: false
annotations: {}
className: "nginx"
pathtype: ImplementationSpecific
hosts:
- host: chart-example.local
paths:
- path: /
tls: []
nameOverride: ""
fullnameOverride: ""
healthNameOverride: ""
lifecycle:
{}
sysctlInitContainer:
enabled: true
keystore: []
networkPolicy:
http:
enabled: false
transport:
enabled: false
tests:
enabled: true
fsGroup: ""
安装:
[root@master-1-230 elasticsearch]# helm upgrade --install es7 -f values.yaml --namespace logging .
Release "es7" does not exist. Installing it now.
NAME: es7
LAST DEPLOYED: Sun Dec 31 17:40:49 2023
NAMESPACE: logging
STATUS: deployed
REVISION: 1
NOTES:
1. Watch all cluster members come up.
$ kubectl get pods --namespace=logging -l app=elasticsearch-master -w2. Test cluster health using Helm test.
$ helm --namespace=logging test es7
查看状态
[root@master-1-230 ~]# kubectl get pod -n logging -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
elasticsearch-master-0 1/1 Running 0 6m41s 10.244.154.55 node-1-233 <none> <none>
elasticsearch-master-1 0/1 Running 0 6m41s 10.244.29.56 node-1-232 <none> <none>
elasticsearch-master-2 1/1 Running 0 6m41s 10.244.167.180 node-1-231 <none> <none>
[root@master-1-230 ~]# kubectl get pv,pvc|grep elasticsearch
persistentvolume/pvc-28127779-54a4-4e81-82aa-f8f4f24a6aa7 50Gi RWO Retain Bound devops/elasticsearch-data-elasticsearch-0 nfs-storageclass 9h
persistentvolume/pvc-9ac444d8-da10-4e87-b97a-a35a96d7b3de 30Gi RWO Retain Bound logging/elasticsearch-master-elasticsearch-master-2 nfs-storageclass 6m55s
persistentvolume/pvc-ae7fe499-1945-458c-a4b6-147a275cb1c3 30Gi RWO Retain Bound logging/elasticsearch-master-elasticsearch-master-0 nfs-storageclass 6m55s
persistentvolume/pvc-fc245bc2-f26c-4ef5-9e7a-1d914bec7191 30Gi RWO Retain Bound logging/elasticsearch-master-elasticsearch-master-1 nfs-storageclass 6m55s
[root@master-1-230 ~]# kubectl get svc -n logging
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
elasticsearch-master ClusterIP 10.98.35.5 <none> 9200/TCP,9300/TCP 7m22s
elasticsearch-master-headless ClusterIP None <none> 9200/TCP,9300/TCP 7m22s
[root@master-1-230 ~]# curl http://10.98.35.5:9200
{
"name" : "elasticsearch-master-1",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "xI-z_KhCTdaHoYLB0hbuQg",
"version" : {
"number" : "7.17.3",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "5ad023604c8d7416c9eb6c0eadb62b14e766caff",
"build_date" : "2022-04-19T08:11:19.070913226Z",
"build_snapshot" : false,
"lucene_version" : "8.11.1",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
[root@master-1-230 ~]# curl http://10.98.35.5:9200/_cat/nodes
10.244.29.56 54 67 25 2.92 6.80 4.34 cdfhilmrstw - elasticsearch-master-1
10.244.167.180 42 69 15 1.20 4.83 2.99 cdfhilmrstw - elasticsearch-master-2
10.244.154.55 50 69 9 4.81 5.00 2.70 cdfhilmrstw * elasticsearch-master-0
[root@master-1-230 ~]# curl http://10.98.35.5:9200/_cat/health
1704016146 09:49:06 elasticsearch green 3 3 2 1 0 0 0 0 - 100.0%
[root@master-1-230 ~]# curl http://10.98.35.5:9200/_cat/master
HMnlmlJrRPiuV3QGxyHyQQ 10.244.154.55 10.244.154.55 elasticsearch-master-0
[root@master-1-230 ~]# curl http://10.98.35.5:9200/_cat/indices
green open .geoip_databases qCNch1AtTTeYlJyROzgT4w 1 1 42 0 79.1mb 39.5mb
三、Kibana 部署配置
使用 helm pull 命令拉取 Kibana Chart 包并解压:
[root@master-1-230 9.3]# helm pull elastic/kibana --untar --version 7.17.3
[root@master-1-230 9.3]# cd kibana/
创建用于安装 Kibana 的 values 文件:
cat values.yaml |egrep -v "#|^$"
[root@master-1-230 kibana]# cat values.yaml |egrep -v "#|^$"
---
elasticsearchHosts: "http://elasticsearch-master:9200"
replicas: 1
extraEnvs:
- name: "NODE_OPTIONS"
value: "--max-old-space-size=1800"
envFrom: []
secretMounts: []
hostAliases: []
image: "docker.elastic.co/kibana/kibana"
imageTag: "7.17.3"
imagePullPolicy: "IfNotPresent"
labels: {}
annotations: {}
podAnnotations: {}
resources:
requests:
cpu: "1000m"
memory: "2Gi"
limits:
cpu: "1000m"
memory: "2Gi"
protocol: http
serverHost: "0.0.0.0"
healthCheckPath: "/app/kibana"
kibanaConfig: {}
podSecurityContext:
fsGroup: 1000
securityContext:
capabilities:
drop:
- ALL
runAsNonRoot: true
runAsUser: 1000
serviceAccount: ""
automountToken: true
priorityClassName: ""
httpPort: 5601
extraVolumes:
[]
extraVolumeMounts:
[]
extraContainers: []
extraInitContainers: []
updateStrategy:
type: "Recreate"
service:
type: ClusterIP
loadBalancerIP: ""
port: 5601
nodePort: ""
labels: {}
annotations:
{}
loadBalancerSourceRanges:
[]
httpPortName: http
ingress:
enabled: true
className: "nginx"
pathtype: ImplementationSpecific
annotations: {}
hosts:
- host: kibana.ikubernetes.cloud
paths:
- path: /
readinessProbe:
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 3
timeoutSeconds: 5
imagePullSecrets: []
nodeSelector: {}
tolerations: []
affinity: {}
nameOverride: ""
fullnameOverride: ""
lifecycle:
{}
安装部署
[root@master-1-230 kibana]# helm upgrade --install kibana -f values.yaml --namespace logging .
Release "kibana" does not exist. Installing it now.
NAME: kibana
LAST DEPLOYED: Sun Dec 31 17:53:59 2023
NAMESPACE: logging
STATUS: deployed
REVISION: 1
TEST SUITE: None
查看状态
[root@master-1-230 kibana]# kubectl get pod -nlogging -o wide |grep kibana
kibana-kibana-679696bbf8-r2ck6 1/1 Running 0 3m34s 10.244.154.43 node-1-233 <none> <none>
[root@master-1-230 kibana]# kubectl get svc -nlogging |grep kibana
kibana-kibana ClusterIP 10.98.48.68 <none> 5601/TCP 3m41s
[root@master-1-230 kibana]# kubectl get ingress -n logging
NAME CLASS HOSTS ADDRESS PORTS AGE
kibana-kibana nginx kibana.ikubernetes.cloud 192.168.1.204 80 36s
四、Fluentd采集组件
4.1 前言
Fluentd 是一个高效的日志聚合器,是用 Ruby 编写的,并且可以很好地扩展。对于大部分企业来说,Fluentd 足够高效并且消耗的资源相对较少。
另外一个工具 Fluent-bit 更轻量级,占用资源更少,但是插件相对 Fluentd 来说不够丰富。
Fluentd 更加成熟,使用更加广泛,所以这里我们使用 Fluentd 来作为日志收集工具。
4.2 工作原理
Fluentd 通过一组给定的数据源抓取日志数据,处理后(转换成结构化的数据格式)将它们转发给其他服务,比如 Elasticsearch、对象存储等等。
Fluentd 支持超过 300 个日志存储和分析服务,在这方面是非常灵活的。
主要运行步骤如下:
- 首先 Fluentd 从多个日志源获取数据;
- 结构化并且标记这些数据;
- 然后根据匹配的标签将数据发送到多个目标服务去;
4.3 日志源配置
收集 Kubernetes 节点上的所有容器日志,就需要做如下的日志源配置:
<source>
@id fluentd-containers.log
@type tail # Fluentd 内置的输入方式,其原理是不停地从源文件中获取新的日志。
path /var/log/containers/*.log # 挂载的宿主机容器日志地址
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 表示通过一个 GET 请求来收集数据。
- path: tail 类型下的特定参数,告诉 Fluentd 采集 /var/log/containers 目录下的所有日志,这是 docker 在 Kubernetes 节点上用来存储运行容器 stdout 输出日志数据的目录。
- pos_file:检查点,如果 Fluentd 程序重新启动了,它将使用此文件中的位置来恢复日志数据收集。
- tag:用来将日志源与目标或者过滤器匹配的自定义字符串,Fluentd 匹配源/目标标签来路由日志数据。
4.4 路由配置
配置将日志数据发送到Elasticsearch
<match **>
@id elasticsearch
@type 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,所以需要配置成 ** 。
- id:目标的一个唯一标识符。
- type:支持的输出插件标识符,我们这里要输出到 Elasticsearch,所以配置成elasticsearch,这是 Fluentd 的一个内置插件。
- log_level:指定要捕获的日志级别,我们这里配置成 info ,表示任何该级别或者该级别以上(INFO、WARNING、ERROR)的日志都将被路由到 Elsasticsearch。
- host/port:定义 Elasticsearch 的地址,也可以配置认证信息,我们的Elasticsearch 不需要认证,所以这里直接指定 host 和 port 即可。
- logstash_format:Elasticsearch 服务对日志数据构建反向索引进行搜索,将logstash_format 设置为 true ,Fluentd 将会以 logstash 格式来转发结构化的日志数据。
- Buffer: Fluentd 允许在目标不可用时进行缓存,比如,如果网络出现故障或者Elasticsearch 不可用的时候。缓冲区配置也有助于降低磁盘的 IO。
4.5 过滤
由于 Kubernetes 集群中应用太多,也有很多历史数据,所以我们可以只将某些应用的日志进行收集,比如我们只采集具有 logging=true 这个 Label 标签的 Pod 日志,这个时候就需要使用 filter,如下所示:
# 删除无用的属性
<filter kubernetes.**>
@type record_transformer
remove_keys $.docker.container_id,$.kubernetes.container_image_id,$.kubernetes.pod_id,$.kubernetes.namespace_id,$.kubernetes.master_url,$.kubernetes.labels.pod-template-hash
</filter>
# 只保留具有logging=true标签的Pod日志
<filter kubernetes.**>
@id filter_log
@type grep
<regexp>
key $.kubernetes.labels.logging
pattern ^true$
</regexp>
</filter>
五、Fluentd部署配置
要收集 Kubernetes 集群的日志,直接用 DasemonSet 控制器来部署 Fluentd 应用,它就可以从 Kubernetes 节点上采集日志,确保在集群中的每个节点上始终运行一个Fluentd 容器。
当然,也可以直接使用 Helm 来进行一键安装。不过为了能够了解更多实现细节,我们这里还是采用手动方法来进行安装。
首先,我们通过 ConfigMap 对象来指定 Fluentd 配置文件,新建 fluentdconfigmap.yaml 文件,文件内容
kind: ConfigMap
apiVersion: v1
metadata:
name: fluentd-conf
namespace: logging
data:
system.conf: |-
<system>
root_dir /tmp/fluentd-buffers/
</system>
fluent.conf: |-
<source>
@id fluentd-containers.log
@type tail # Fluentd 内置的输入方式,其原理是不停地从源文件中获取新的日志。
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>
# 在日志输出中检测异常,并将其作为一条日志转发
# https://github.com/GoogleCloudPlatform/fluent-plugin-detect-exceptions
<match raw.kubernetes.**> # 匹配tag为raw.kubernetes.**日志信息
@id kubernetes
@type detect_exceptions # 使用detect-exceptions插件处理异常栈信息
remove_tag_prefix raw # 移除 raw 前缀
message log
stream stream
multiline_flush_interval 5
max_bytes 500000
max_lines 1000
</match>
<filter **> # 拼接日志
@id filter_concat
@type concat # Fluentd Filter 插件,用于连接多个事件中分隔的多行日志。
key message
multiline_end_regexp /\n$/ # 以换行符“\n”拼接
separator ""
</filter>
# 添加 Kubernetes metadata 数据
<filter kubernetes.**>
@id filter_kubernetes_metadata
@type kubernetes_metadata
</filter>
# 修复 ES 中的 JSON 字段
# 插件地址:https://github.com/repeatedly/fluent-plugin-multi-format-parser
<filter kubernetes.**>
@id filter_parser
@type parser # multi-format-parser多格式解析器插件
key_name log # 在要解析的记录中指定字段名称。
reserve_data true # 在解析结果中保留原始键值对。
remove_key_name_field true # key_name 解析成功后删除字段。
<parse>
@type multi_format
<pattern>
format json
</pattern>
<pattern>
format none
</pattern>
</parse>
</filter>
# 删除一些多余的属性
<filter kubernetes.**>
@type record_transformer
remove_keys $.kubernetes.namespace_labels.project,$.kubernetes.pod_ip,$.kubernetes.labels.app,$.docker.container_id,$.kubernetes.container_image_id,$.kubernetes.pod_id,$.kubernetes.namespace_id,$.kubernetes.master_url,$.kubernetes.labels.pod-template-hash
</filter>
# 只保留具有logging=true标签的Pod日志
<filter kubernetes.**>
@id filter_log
@type grep
<regexp>
key $.kubernetes.labels.logging
pattern ^true$
</regexp>
</filter>
<match **>
@id elasticsearch
@type elasticsearch
@log_level info
include_tag_key true
host elasticsearch-master.logging.svc
port 9200
logstash_format true
logstash_prefix k8slog # 设置 index 前缀为 k8slog
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 容器日志目录,收集到数据经过处理后发送到elasticsearch-client:9200 服务。
注意:挂载的日志目录出现 unreadable 说明日志软连接有问题,无法读取日志。
如果有小伙伴,更改了docker的数据目录,这个时候需要更改为对应的数据目录,尤其是/var/log/pods/:
在我的 ds.yaml 中,必须挂载/data/docker/containers 而不是/var/lib/containers/
volumeMounts:
- name: fluentconfig
mountPath: /etc/fluent/config.d
- name: varlog
mountPath: /var/log
- name: varlogpods
mountPath: /var/log/pods
- name: datadockercontainers
mountPath: /data/docker/containers
volumes:
- name: fluentconfig
configMap:
name: fluentd-conf
- name: varlog
hostPath:
path: /var/log
- name: varlogpods
hostPath:
path: /var/log/pods
- name: datadockercontainers
hostPath:
path: /data/docker/containers
然后新建一个 fluentd-daemonset.yaml 的文件,文件内容如下:
apiVersion: v1
kind: ServiceAccount
metadata:
name: fluentd-es
namespace: logging
labels:
k8s-app: fluentd-es
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: fluentd-es
labels:
k8s-app: fluentd-es
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
rules:
- apiGroups:
- ""
resources:
- "namespaces"
- "pods"
verbs:
- "get"
- "watch"
- "list"
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: fluentd-es
labels:
k8s-app: fluentd-es
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
subjects:
- kind: ServiceAccount
name: fluentd-es
namespace: logging
apiGroup: ""
roleRef:
kind: ClusterRole
name: fluentd-es
apiGroup: ""
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
namespace: logging
labels:
app: fluentd
kubernetes.io/cluster-service: "true"
spec:
selector:
matchLabels:
app: fluentd
template:
metadata:
labels:
app: fluentd
kubernetes.io/cluster-service: "true"
spec:
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
serviceAccountName: fluentd-es
containers:
- name: fluentd
image: quay.io/fluentd_elasticsearch/fluentd:v3.4.0
volumeMounts:
- name: fluentconfig
mountPath: /etc/fluent/config.d
- name: varlog
mountPath: /var/log
- name: varlogpods
mountPath: /var/log/pods
volumes:
- name: fluentconfig
configMap:
name: fluentd-conf
- name: varlog
hostPath:
path: /var/log
- name: varlogpods
hostPath:
path: /var/log/pods
[root@master-1-230 9.3]# kubectl apply -f 8.yaml
serviceaccount/fluentd-es created
clusterrole.rbac.authorization.k8s.io/fluentd-es created
clusterrolebinding.rbac.authorization.k8s.io/fluentd-es created
daemonset.apps/fluentd created
我们将上面创建的 fluentd-config 这个 ConfigMap 对象通过 volumes 挂载到了Fluentd 容器中。
场景:为了能够灵活控制哪些节点的日志可以被收集,还可以添加了一个 nodSelector
属性:哪台节点上的日志需要采集,那么我们就需要给节点打上上面的标签。
nodeSelector:
beta.kubernetes.io/fluentd-ds-ready: "true"
如果你需要在其他节点上采集日志,则需要给对应节点打上标签,使用如下命令:
kubectl label nodes <node_name> beta.kubernetes.io/fluentd-ds-ready=true
另外集群使用的是 kubeadm 搭建的,默认情况下 master 节点有污点,所以如果想要也收集 master 节点的日志,则需要添加上容忍:
tolerations:
- operator: Exists
分别创建上面的 ConfigMap 对象和 DaemonSet:
创建完成后,查看对应的 Pods 列表,检查是否部署成功:
[root@master-1-230 9.3]# kubectl get pods -n logging
NAME READY STATUS RESTARTS AGE
elasticsearch-master-0 1/1 Running 0 37m
elasticsearch-master-1 1/1 Running 0 37m
elasticsearch-master-2 1/1 Running 0 37m
fluentd-2nqv2 1/1 Running 0 2m39s
fluentd-dtsgw 1/1 Running 0 2m39s
fluentd-tp6lk 1/1 Running 0 2m39s
kibana-kibana-679696bbf8-r2ck6 1/1 Running 0 23m
Fluentd 启动成功后,就可以发送日志到 ES 了,但是我们这里是过滤了只采集具有logging=true 标签的 Pod 日志,所以现在还没有任何数据会被采集。
下面我们部署一个简单的测试应用, 新建 counterlog.yaml 文件,文件内容如下:
apiVersion: v1
kind: Pod
metadata:
name: counterlog
labels:
logging: "true" # 一定要具有该标签才会被采集
spec:
containers:
- name: count
image: busybox
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@master-1-230 9.3]# kubectl apply -f 9.yaml
pod/counterlog created
[root@master-1-230 9.3]# kubectl get pods|grep counterlog
counterlog 1/1 Running 0 53s
也可以手动测试下,是否kibana成功绑定了es集群;
手动推送一条测试数据
[root@master-1-230 9.3]# curl -X POST -H "Content-Type: application/json" -d '{
> "message": "This is a test log message",
> "timestamp": "2023-12-31T10:00:00",
> "source": "kubernets.cn"
> }' http://10.98.35.5:9200/mylog/_doc
{"_index":"mylog","_type":"_doc","_id":"0oJkv4wB-7nKRR65_wA4","_version":1,"result":"created","_shards":{"total":2,"successful":2,"failed":0},"_seq_no":0,"_primary_term":1}
1. 选择"Stack Management"(堆栈管理)。
2. 在堆栈管理界面中,您将看到一个"Data"(数据)菜单。在该菜单中,选择"Index
Management"(索引管理)。这将显示 Elasticsearch 中的索引列表。
Pod 创建并运行后,回到 Kibana Dashboard 页面,点击左侧最下面的 Management ->Stack Management ,进入管理页面,点击左侧 Kibana 下面的 索引模式 ,点击 创建索引模式 开始导入索引数据:
在这里可以配置我们需要的 Elasticsearch 索引,前面 Fluentd 配置文件中我们采集的日志使用的是 logstash 格式,定义了一个 k8s 的前缀,所以这里只需要在文本框中输入k8slog-* 即可匹配到 Elasticsearch 集群中采集的 Kubernetes 集群日志数据,然后点击下一步,进入以下页面:
在该页面中配置使用哪个字段按时间过滤日志数据,在下拉列表中,选择 @timestamp字段,然后点击 创建索引模式 ,创建完成后,点击左侧导航菜单中的 Discover ,然后就可以看到一些直方图和最近采集到的日志数据了:
现在的数据就是上面 Counter 应用的日志,如果还有其他的应用,我们也可以筛选过滤:
我们也可以通过其他元数据来过滤日志数据,比如您可以单击任何日志条目以查看其他元数据,如容器名称,Kubernetes 节点,命名空间等。
六、总结
在 Kubernetes(K8S)架构下使用 EFK(Elasticsearch + Fluentd + Kibana)服务,具有如下优势:
- 日志集中管理:EFK 提供了一个集中的平台来收集、存储和管理容器化应用程序的日志。它能够从多个容器和节点收集日志,并将其发送到集中的 Elasticsearch 数据存储中。
- 可扩展性:Elasticsearch 和 Fluentd 支持水平扩展,可以根据需要增加节点或副本数量,以适应日志量的增长。
- 实时日志分析:通过 Kibana 可视化界面,EFK 提供了实时的日志分析和查询功能。可以轻松地搜索、过滤和分析大量的日志数据,以便进行故障排除、性能优化和监控。
- 灵活的日志解析:Fluentd 是一个高度可配置的日志收集代理,可以轻松进行日志解析、过滤和转换。它支持多种输入和输出插件,可以适应各种应用程序和日志格式。
- 强大的搜索和聚合功能:Elasticsearch 是一个分布式搜索和分析引擎,具有强大的搜索和聚合功能。它可以通过全文搜索、关键字过滤、聚合和可视化等功能,帮助用户快速定位和分析关键日志信息。
- 集成与生态系统:EFK 与 Kubernetes 生态系统紧密集成,支持通过标签过滤、命名空间隔离和动态配置等方式来灵活管理日志收集和展示。此外,EFK 还可以与其他监控和告警工具集成,以提供全面的应用程序运行时监控和报警能力
标签:fluentd,log,平台,master,日志,K8S,root,name From: https://www.cnblogs.com/pythonlx/p/17937519