# Kubernetes(K8s)笔记Day17:使用 EFK 收集和过滤 K8s 日志信息

概述

在Kubernetes集群上运行多个服务和应用程序时,日志收集系统可以帮助我们快速分类和分析由Pod生成的大量日志数据。Kubernetes中比较流行的日志收集解决方案是Elasticsearch、Fluentd和Kibana(EFK)技术栈,也是官方推荐的一种方案。

EFK 正是从 ELK 演变而来的,这一演变的核心,是日志收集器从 Logstash 替换成了更轻量、高效的 Fluentd 或 Filebea

一、ELK/EFK日志流程的几种常见方案

方案编号 数据流向
方案1 Logstash(采集、处理)→ Elasticsearch(存储)→ Kibana(展示)
方案2 Logstash(采集)→ Logstash(聚合、处理)→ Elasticsearch(存储)→ Kibana
方案3 Filebeat(采集、处理)→ Elasticsearch(存储)→ Kibana
方案4 Filebeat(采集)→ Logstash(聚合、处理)→ Elasticsearch(存储)→ Kibana
方案5 Filebeat(采集)→ Kafka/Redis(消峰)→ Logstash(聚合、处理)→ Elasticsearch(存储)→ Kibana

说明:方案5引入Kafka或Redis作为消息队列,用于缓解突增日志流量对Elasticsearch的压力,起到流量削峰填谷的作用。

在K8s的日志采集方案中,使用 Filebeat 进行采集的方案最常见,最泛用

二、Fluentd介绍

2.1 什么是Fluentd

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

Fluentd是随着Docker和Elasticsearch一起流行起来的日志采集Agent

2.2 Fluentd优势

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

三、安装Elasticsearch组件

3.1 创建命名空间

在安装Elasticsearch集群之前,我们先创建一个名称空间,在这个名称空间下安装日志收集工具Elasticsearch、Fluentd、Kibana。我们创建一个kube-logging名称空间,将EFK组件安装到该名称空间中。

bash 复制代码
[root@hd1 ~]# vim kube-logging.yaml
yaml 复制代码
kind: Namespace
apiVersion: v1
metadata:
  name: kube-logging

或者直接使用命令创建:kubectl create namespace kube-logging

bash 复制代码
[root@hd1 ~]# kubectl apply -f kube-logging.yaml

# 查看命名空间
[root@hd1 ~]# kubectl get namespaces | grep kube-logging
# 显示如下,说明创建成功:
kube-logging      Active   86s

3.2 安装Elasticsearch的Service

创建一个Headless Service 的Kubernetes服务,服务名称是elasticsearch。Headless Service不具备负载均衡也没有ClusterIP。

普通Service与Headless Service对比
特性 普通Service(ClusterIP) Headless Service(clusterIP: None)
负载均衡实现 内置 :由kube-proxy(如iptables/IPVS)实现,流量在多个Pod间自动分发 不内置 :不提供负载均衡,kube-proxy不参与处理(通过core dns实现"dns层面"的负载均衡)
访问方式 通过一个**虚拟IP(ClusterIP)**访问 无虚拟IP,客户端直接获取所有Pod的IP列表
DNS解析结果 返回服务的虚拟IP(ClusterIP) 返回一组Pod的IP地址列表
bash 复制代码
[root@hd1 ~]# vim elasticsearch_svc.yaml
yaml 复制代码
kind: Service
apiVersion: v1
metadata:
  name: elasticsearch
  namespace: kube-logging
  labels:
    app: elasticsearch
spec:
  selector:  #确认选择的标签
    app: elasticsearch
  clusterIP: None      #无头服务
  ports:  #这里暴露es的两个端口
    - port: 9200    #es业务端口
      name: rest 
    - port: 9300    # Elasticsearch 集群内部的节点通信接口
      name: inter-node
bash 复制代码
[root@hd1 ~]# kubectl apply -f elasticsearch_svc.yaml

配置解析

  • kube-logging名称空间定义了一个名为elasticsearch的Service,带有app=elasticsearch标签
  • 当将Elasticsearch StatefulSet与此服务关联时,服务将返回带有标签app=elasticsearch的Elasticsearch Pods的DNS A记录
  • 设置clusterIP: None,将该服务设置成无头服务
  • 定义端口9200(用于REST API交互)和9300(用于节点间通信)
bash 复制代码
# 查看elasticsearch的service是否创建成功
[root@hd1 ~]# kubectl get services --namespace=kube-logging
NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)          
elasticsearch   ClusterIP   None         <none>        9200/TCP,9300/TCP

3.3 创建StorageClass(存储类动态供给)

通过StatefulSet创建Elasticsearch集群,需要先创建StorageClass实现存储类动态供给。

3.3.1 安装NFS服务

注意:选择K8s集群的hd1节点作为NFS服务端,其IP为192.168.1.11。

bash 复制代码
# yum安装nfs(所有节点执行)
[root@hd1 ~]# yum install nfs-utils -y

# 启动nfs服务
[root@hd1 ~]# systemctl start nfs

# 设置nfs开机自启动
[root@hd1 ~]# systemctl enable nfs.service

# 在hd1上创建一个nfs共享目录
[root@hd1 ~]# mkdir /data/v1 -p

# 编辑/etc/exports文件
[root@hd1 ~]# vim /etc/exports
/data/v1 192.168.1.0/24(rw,no_root_squash)

# 加载配置,使配置生效
[root@hd1 ~]# exportfs -arv
[root@hd1 ~]# systemctl restart nfs
3.3.2 创建NFS Provisioner

(1)创建ServiceAccount

bash 复制代码
[root@hd1 efk]# vim serviceaccount.yaml
yaml 复制代码
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-provisioner
bash 复制代码
[root@hd1 efk]# kubectl apply -f serviceaccount.yaml
#查看
[root@hd1 ~]# kubectl get sa
NAME               SECRETS   AGE
nfs-provisioner    0         28s

(2)RBAC授权

bash 复制代码
[root@hd1]# vim rbac.yaml
yaml 复制代码
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
  - apiGroups: [""]
    resources: ["services", "endpoints"]
    verbs: ["get"]
  - apiGroups: ["extensions"]
    resources: ["podsecuritypolicies"]
    resourceNames: ["nfs-provisioner"]
    verbs: ["use"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-provisioner
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-provisioner
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-provisioner
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-provisioner
  apiGroup: rbac.authorization.k8s.io
bash 复制代码
[root@hd1]# kubectl apply -f rbac.yaml

配置详细解析:

bash 复制代码
# =============================================================================
# 第一段:ClusterRole - 定义 NFS Provisioner 在整个集群中的"权力清单"
# =============================================================================
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-provisioner-runner
rules:
  # 核心:监听 PVC 请求,动态创建/删除 PV
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]   # 增删改查 PV
  
  # 核心:触发源,创建 PV 后需更新 PVC 状态完成绑定
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]            # 监听 PVC,更新绑定状态

  # 配置:读取 StorageClass 参数(如 NFS 服务端地址)
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]

  # 排错:记录失败事件到 kubectl describe 中
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]

  # 探测:基础环境发现(可忽略,不影响主流程)
  - apiGroups: [""]
    resources: ["services", "endpoints"]
    verbs: ["get"]

  # 安全:若集群启用 PSP,此条必须保留(K8s >=1.25 需将 apiGroups 改为 ["policy"])
  - apiGroups: ["extensions"]
    resources: ["podsecuritypolicies"]
    resourceNames: ["nfs-provisioner"]
    verbs: ["use"]

---
# =============================================================================
# 第二段:ClusterRoleBinding - 把上述"权力"授予服务账户(SA)
# =============================================================================
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-provisioner
    namespace: default          # ⚠️ 陷阱:此处必须改为实际的部署命名空间
roleRef:
  kind: ClusterRole
  name: nfs-provisioner-runner
  apiGroup: rbac.authorization.k8s.io

---
# =============================================================================
# 第三段:Role - 实现"抢锁选主"(Leader Election),防止多副本同时创建 PV
# =============================================================================
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-provisioner
  # namespace: 未指定,默认归 default  ⚠️ 陷阱:必须显式加上,与 Deployment 同命名空间
rules:
  - apiGroups: [""]
    resources: ["endpoints"]    # 充当分布式锁对象
    verbs: ["get", "list", "watch", "create", "update", "patch"]  # 抢锁(写)、续约(更新)

---
# =============================================================================
# 第四段:RoleBinding - 把"选主权"绑定给 SA
# =============================================================================
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-provisioner
  # namespace: 未指定,默认归 default  ⚠️ 陷阱:必须与第三段及 SA 命名空间一致
subjects:
  - kind: ServiceAccount
    name: nfs-provisioner
    namespace: default          # ⚠️ 陷阱:必须改为实际部署的命名空间
roleRef:
  kind: Role
  name: leader-locking-nfs-provisioner
  apiGroup: rbac.authorization.k8s.io

不会在生产环境徒手敲这上百行的 YAML。通常,更加建议:

  • 使用 Helm(包管理器)------ 绝对的主流
  • 拷贝官方文档的"Production Ready"示例
  • 使用 Operator(针对复杂有状态应用)
  • AI 辅助

(3)导入NFS Provisioner镜像

bash 复制代码
# 把nfs-subdir-external-provisioner.tar.gz上传到hd2节点,手动导入
[root@hd2 ~]# ctr -n k8s.io images import nfs-subdir-external-provisioner.tar.gz
# 或者(如果使用docker)
[root@hd2 ~]# docker load -i nfs-subdir-external-provisioner.tar.gz

(4)通过Deployment创建NFS Provisioner

bash 复制代码
[root@hd1 ~]# vim deployment.yaml
yaml 复制代码
kind: Deployment
apiVersion: apps/v1
metadata:
  name: nfs-provisioner
spec:
  selector:
    matchLabels:
      app: nfs-provisioner
  replicas: 1
  strategy:  #部署策略
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-provisioner
    spec:
      serviceAccount: nfs-provisioner   #关联SA
      containers:
        - name: nfs-provisioner
          image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:   #关键环境变量
            - name: PROVISIONER_NAME
              value: example.com/nfs
            - name: NFS_SERVER
              value: 192.168.1.11
            - name: NFS_PATH
              value: /data/v1
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.1.11
            path: /data/v1
bash 复制代码
[root@hd1 ~]# kubectl apply -f deployment.yaml

# 验证nfs是否创建成功
[root@hd1 ~]# kubectl get pods | grep nfs
# 显示如下说明创建成功:
nfs-provisioner-5975849bb4-92dhq   1/1     Running     3          11h

(5)创建StorageClass

bash 复制代码
[root@hd1 ~]# vim class.yaml
yaml 复制代码
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: do-block-storage
provisioner: example.com/nfs
bash 复制代码
[root@hd1 ~]# kubectl apply -f class.yaml

重要provisioner: example.com/nfs 该值需要和NFS Provisioner配置的PROVISIONER_NAME处的value值保持一致。

3.4 安装Elasticsearch集群(StatefulSet)

bash 复制代码
# 把elasticsearch_7_2_0.tar.gz和busybox.tar.gz上传到hd2,手动导入:
[root@hd2 ~]# ctr -n k8s.io images import elasticsearch_7_2_0.tar.gz
[root@hd3 ~]# ctr -n k8s.io images import busybox.tar.gz
# 或者(如果使用docker)
[root@hd2 ~]# docker load  -i elasticsearch_7_2_0.tar.gz
[root@hd3 ~]# docker load -i busybox.tar.gz
bash 复制代码
[root@hd1 ~]# cat elasticsearch-statefulset.yaml
yaml 复制代码
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: es-cluster
  namespace: kube-logging
spec:
  serviceName: elasticsearch   # 关联到之前创建的 Headless Service(无头服务)
  replicas: 3
  selector:
    matchLabels:
      app: elasticsearch
  template:     #pod模板
    metadata:
      labels:
        app: elasticsearch
    spec:      #容器规格
      containers:
      - name: elasticsearch    
        image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0
        imagePullPolicy: IfNotPresent
        resources:
            limits:
              cpu: 1000m
            requests:
              cpu: 100m
        ports:
        - containerPort: 9200  # 对外提供 Restful API(增删改查)
          name: rest
          protocol: TCP
        - containerPort: 9300  # 集群内部节点间通信(选主、数据同步)
          name: inter-node
          protocol: TCP
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
        env:     #环境变量(Env
          - name: cluster.name ## 给集群起名
            value: k8s-logs
          - name: node.name  #节点名,这里是读取 Pod 的 metadata.name(即 Pod 的名字)作为 ES 的节点名
            valueFrom:
              fieldRef:   #属性来自字段
                fieldPath: metadata.name  #前面我们定义metadata中的name
          - name: discovery.seed_hosts   #种子节点发现列表,为什么必须写完整的 FQDN
            value: "es-cluster-0.elasticsearch.kube-logging.svc.cluster.local,es-cluster-1.elasticsearch.kube-logging.svc.cluster.local,es-cluster-2.elasticsearch.kube-logging.svc.cluster.local"
          - name: cluster.initial_master_nodes  # 第一次启动谁有资格当主节点
            value: "es-cluster-0,es-cluster-1,es-cluster-2"
          - name: ES_JAVA_OPTS     # 给 JVM 分配 512MB 堆内存
            value: "-Xms512m -Xmx512m"
      initContainers:   #初始化容器
      - name: fix-permissions     # 作用:修复数据目录权限。ES 进程用户 ID 是 1000,不修改权限,写不进 NFS 目录。
        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     # 作用:修改宿主机内核参数。ES 需要大量内存映射区,默认值太小,不调这个 Pod 会因内存不足报错崩溃。
        image: busybox:1.28
        imagePullPolicy: IfNotPresent
        command: ["sysctl", "-w", "vm.max_map_count=262144"]
        securityContext: #安全上下文
          privileged: true
      - name: increase-fd-ulimit    # 作用:提高文件描述符限制。ES 会打开大量文件(索引段),默认 1024 太小。
        image: busybox:1.28
        imagePullPolicy: IfNotPresent
        command: ["sh", "-c", "ulimit -n 65536"]
        securityContext:
          privileged: true
  volumeClaimTemplates:
  - metadata:
      name: data
      labels:
        app: elasticsearch
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: nfs
      resources:
        requests:
          storage: 10Gi
bash 复制代码
[root@hd1 ~]# kubectl apply -f elasticsearch-statefulset.yaml
资源清单详细解析

(1)StatefulSet主体部分

kube-logging的名称空间中定义了一个名为es-cluster的StatefulSet。使用serviceName: elasticsearch字段与之前创建的Headless ElasticSearch服务相关联。这样可以确保可以使用以下DNS地址访问StatefulSet中的每个Pod:es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local,其中[0,1,2]与Pod分配的序号数相对应。

指定3个replicas(3个Pod副本),将selector.matchLabels设置为app: elasticsearch.spec.selector.matchLabels.spec.template.metadata.labels字段必须匹配。

(2)Pod模板中的容器配置

配置项 说明
容器名 elasticsearch -
镜像 docker.elastic.co/elasticsearch/elasticsearch:7.2.0 -
CPU请求 100m 容器至少需要0.1个vCPU
CPU限制 1000m 容器最多可以使用1个vCPU
端口9200 rest REST API交互端口
端口9300 inter-node 节点间通信端口
数据持久化 volumeMounts: data → /usr/share/elasticsearch/data 挂载数据卷

(3)环境变量说明

环境变量 说明
cluster.name k8s-logs Elasticsearch集群的名称
node.name metadata.name 节点名称,解析为es-cluster-0,1,2
discovery.seed_hosts es-cluster-0.elasticsearch... 节点相互发现的静态主机列表
cluster.initial_master_nodes es-cluster-0,es-cluster-1,es-cluster-2 初始主节点列表
ES_JAVA_OPTS -Xms512m -Xmx512m JVM堆内存大小(最小和最大都是512MB)

补充 :由于所有Pod都在同一个kube-logging名称空间下,discovery.seed_hosts可以简写为es-cluster-[0,1,2].elasticsearch(省略svc.cluster.local后缀)。

(4)InitContainer(初始化容器)

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

InitContainer名称 作用 命令
fix-permissions 将Elasticsearch数据目录的用户和组更改为1000:1000(Elasticsearch用户的UID) chown -R 1000:1000 /usr/share/elasticsearch/data
increase-vm-max-map 增加操作系统对mmap计数的限制(默认值可能太低,导致内存不足错误) sysctl -w vm.max_map_count=262144
increase-fd-ulimit 增加打开文件描述符的最大数量 ulimit -n 65536

重要 :默认情况下,Kubernetes用root用户挂载数据目录,这会使得Elasticsearch无法访问该数据目录,因此需要通过fix-permissions初始化容器修复权限。
补充:Elasticsearch生产环境文档还建议禁用swap分区以提高性能。

(5)volumeClaimTemplates(持久化存储模板)

yaml 复制代码
volumeClaimTemplates:
- metadata:
    name: data
    labels:
      app: elasticsearch
  spec:
    accessModes: [ "ReadWriteOnce" ]
    storageClassName: nfs     #指定已存在的存储类
    resources:
      requests:
        storage: 10Gi
  • 使用volumeClaimTemplates定义持久化模板,Kubernetes会使用它为每个Pod创建PersistentVolume
  • 访问模式为ReadWriteOnce,意味着只能被mount到单个节点上进行读写
  • 使用名为do-block-storage的StorageClass对象(提前创建好的NFS Provisioner)
验证Elasticsearch集群是否部署成功
bash 复制代码
#### 验证Elasticsearch集群是否部署成功

```bash
# 查看es的pod是否创建成功
[root@hd1 ~]# kubectl get pods -n kube-logging

执行上述命令后,预期会看到类似如下的输出,表明 Elasticsearch 集群的三个 Pod 已成功创建并正常运行:

bash 复制代码
NAME           READY   STATUS    RESTARTS   AGE
es-cluster-0   1/1     Running   0          55s
es-cluster-1   1/1     Running   0          35s
es-cluster-2   1/1     Running   0          28s

输出字段解读

  • NAME : Pod 名称,遵循 StatefulSet 的命名规则 es-cluster-<序号>
  • READY : 1/1 表示 Pod 中唯一的容器已准备就绪。
  • STATUS : Running 是期望状态,表示 Pod 正在运行。如果看到 PendingContainerCreatingError,则需要进一步排查。
  • RESTARTS : 重启次数。0 表示运行稳定。非零值可能表明容器曾崩溃或健康检查失败。
  • AGE: Pod 已运行的时间。

常见问题与排查

  1. Pod 处于 Pending 状态 :通常是由于资源(CPU/内存)不足或节点选择器/污点导致无法调度。使用 kubectl describe pod es-cluster-0 -n kube-logging 查看事件详情。
  2. Pod 处于 ContainerCreating 状态过久:可能是镜像拉取失败或持久卷声明(PVC)未成功绑定。检查镜像仓库可访问性及 StorageClass 配置。
  3. Pod 处于 CrashLoopBackOff 状态 :容器启动后立即退出。查看容器日志:kubectl logs es-cluster-0 -n kube-logging。常见原因包括:
    • 数据目录 /usr/share/elasticsearch/data 权限问题(fix-permissions InitContainer 未成功执行)。
    • JVM 内存参数 ES_JAVA_OPTS 设置不当,导致内存不足。
    • Elasticsearch 节点发现失败(检查 discovery.seed_hosts 配置的 DNS 解析)。

进一步验证服务

确认与 StatefulSet 关联的 Headless Service 也创建成功:

bash 复制代码
[root@hd1 ~]# kubectl get svc -n kube-logging

预期输出应包含 elasticsearch 服务,且 CLUSTER-IPNone

bash 复制代码
NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)             
elasticsearch   ClusterIP   None         <none>        9200/TCP,9300/TCP

root@hd1 \~# kubectl get svc -n kube-logging

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)

elasticsearch ClusterIP None 9200/TCP,9300/TCP

复制代码
#### 通过REST API验证Elasticsearch集群状态

使用`kubectl port-forward`将本地端口9200转发到Elasticsearch节点:

```bash
[root@hd1 ~]# kubectl port-forward es-cluster-0 9200:9200 --namespace=kube-logging

在另一个终端窗口中执行如下请求:

bash 复制代码
curl http://localhost:9200/_cluster/state?pretty

输出示例:

json 复制代码
{
  "cluster_name" : "k8s-logs",
  "compressed_size_in_bytes" : 348,
  "cluster_uuid" : "QD06dK7CQgids-GQZooNVw",
  "version" : 3,
  "state_uuid" : "mjNIWXAzQVuxNNOQ7xR-qg",
  "master_node" : "IdM5B7cUQWqFgIHXBp0JDg",
  "blocks" : { },
  "nodes" : {
    "u7DoTpMmSCixOoictzHItA" : {
      "name" : "es-cluster-1",
      "ephemeral_id" : "ZlBflnXKRMC4RvEACHIVdg",
      "transport_address" : "10.244.8.2:9300",
      "attributes" : { }
    },
    "IdM5B7cUQWqFgIHXBp0JDg" : {
      "name" : "es-cluster-0",
      "ephemeral_id" : "JTk1FDdFQuWbSFAtBxdxAQ",
      "transport_address" : "10.244.44.3:9300",
      "attributes" : { }
    },
    "R8E7xcSUSbGbgrhAdyAKmQ" : {
      "name" : "es-cluster-2",
      "ephemeral_id" : "9wv6ke71Qqy9vk2LgJTqaA",
      "transport_address" : "10.244.40.4:9300",
      "attributes" : { }
    }
    ......
  }
}

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

四、安装Kibana

bash 复制代码
# 把kibana_7_2_0.tar.gz上传到hd2和hd3节点,手动导入:
[root@hd2 ~]# ctr -n k8s.io images import kibana_7_2_0.tar.gz
# 或者(如果使用docker)
[root@hd2 ~]# docker load -i kibana_7_2_0.tar.gz

查看yaml清单内容

bash 复制代码
[root@hd1.com]# vim kibana.yaml
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: docker.elastic.co/kibana/kibana:7.2.0
        imagePullPolicy: IfNotPresent
        resources:
          limits:
            cpu: 1000m
          requests:
            cpu: 100m
        env:
          - name: ELASTICSEARCH_URL
            value: http://elasticsearch:9200  :  #这里是 es 的 service 的短域名
        ports:
        - containerPort: 5601

service 没有显式指定 type 字段,它默认就是 ClusterIP 类型

bash 复制代码
[root@hd1 ~]# kubectl apply -f kibana.yaml

配置解析

  • 定义了两个资源对象:一个Service和一个Deployment
  • Kibana Pod中使用ELASTICSEARCH_URL环境变量设置Elasticsearch集群的端点和端口
  • 直接使用Kubernetes DNS http://elasticsearch:9200,此端点对应服务名称为elasticsearch
  • 由于是一个Headless Service,该域将解析为3个Elasticsearch Pod的IP地址列表
bash 复制代码
# 查看Kibana是否部署成功
[root@hd1 ~]# kubectl get pods -n kube-logging
NAME                      READY   STATUS    RESTARTS   AGE
es-cluster-0              1/1     Running   0          170m
es-cluster-1              1/1     Running   0          170m
es-cluster-2              1/1     Running   0          170m
kibana-5749b5778b-c9djr   1/1     Running   0          4m3s

[root@hd1 ~]# kubectl get svc -n kube-logging
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             
elasticsearch   ClusterIP   None             <none>        9200/TCP,9300/TCP   
kibana          ClusterIP   10.106.209.160   <none>        5601/TCP            

可以看到,因为service的类型没有指定,实验默认是cluster,并没有暴露出来

修改Service类型为NodePort(对外暴露访问)
bash 复制代码
[root@hd1.com]# kubectl edit svc kibana -n kube-logging
# 把 type: ClusterIP 改成 type: NodePort
# 保存退出
bash 复制代码
[root@hd1 ~]# kubectl get svc -n kube-logging
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             
elasticsearch   ClusterIP   None             <none>        9200/TCP,9300/TCP   
kibana          NodePort    10.106.209.160   <none>        5601:31966/TCP  
#这时候它的映射端口就暴露出来了    

在浏览器中打开 http://<任意节点IP>:31966 即可访问Kibana界面。

五、安装Fluentd组件

Fluentd 是我们用来进行信息采集 的组件,使用DaemonSet 控制器部署Fluentd组件,这样可以保证集群中的每个节点都可以运行同样的Fluentd Pod副本,从而可以收集K8s集群中每个节点的日志。

在K8s集群中,容器应用程序的输入输出日志会重定向到Node节点里的JSON文件中,Fluentd可以tail(跟踪)和过滤以及把日志转换成指定的格式发送到Elasticsearch集群中。

补充:除了容器日志,Fluentd也可以采集kubelet、kube-proxy、docker的日志。

bash 复制代码
# 离线镜像压缩包fluentd.tar.gz上传到hd1,hd2和hd3上,手动导入:
[root@hd1 ~]# ctr -n k8s.io images import fluentd.tar.gz
# 或者(如果使用docker)
[root@hd1 ~]# docker load -i fluentd.tar.gz
bash 复制代码
[root@hd1 ~]# cat fluentd.yaml
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/control-plane
        effect: NoSchedule
      containers:
      - name: fluentd
        image: fluent/fluentd-kubernetes-daemonset:v1.4.2-debian-elasticsearch-1.1
        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
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

这段配置的作用:

  • 第一段:
    • 创建了一个名为 fluentd 的服务账户,专门用于 Fluentd Pod
  • 第二段和第三段:
    • 定义 Fluentd 对 pods 和 namespaces 这两个资源拥有读取权限(get、list、watch)
    • 把第二段定义的权限绑定给 kube-logging 命名空间下的 fluentd 服务账户
  • 第四段
    • 允许 Fluentd Pod 调度到控制平面节点(Master/Control-plane)
    • 告诉 Fluentd 要将日志发送到哪个 Elasticsearch 集群
    • 告诉 Fluentd 要将日志发送到哪个 Elasticsearch 集群
      • 这里使用的是 K8s 集群内的完整 DNS 域名(FQDN):elasticsearch.kube-logging.svc.cluster.local,其中elasticsearch是之前创建的 Headless Service 名称
    • 将宿主机的日志目录挂载到 Fluentd 容器内部
bash 复制代码
[root@hd1 ~]# kubectl apply -f fluentd.yaml

配置解析

  • ServiceAccount和RBAC:为Fluentd Pod赋予访问Kubernetes API的权限,用于获取Pod和Namespace的元数据信息(如Pod名称、Namespace名称等),这些信息会作为日志的标签发送到Elasticsearch,方便后续按Pod/Namespace维度检索。
  • DaemonSet:保证每个节点运行一个Fluentd Pod
  • Tolerations:允许调度到控制平面节点(master节点),采集master组件的日志
  • 环境变量:配置Elasticsearch的地址(使用完整的Kubernetes DNS域名)、端口和协议
  • VolumeMounts
    • /var/log:宿主机系统日志目录(包含kubelet、kube-proxy等组件日志)
    • /var/lib/docker/containers:Docker容器日志存储目录(只读挂载)
  • hostPath:直接挂载宿主机目录到容器中
bash 复制代码
# 查看fluentd是否部署成功
[root@hd1 ~]# kubectl get pods -n kube-logging
NAME                 READY   STATUS    RESTARTS  
es-cluster-0             1/1      Running   0          
es-cluster-1             1/1      Running   0          
es-cluster-2             1/1      Running   0         
fluentd-dszg7           1/1      Running   0         
fluentd-dwk7t           1/1      Running   0       
kibana-84cf7f59c-2546x  1/1     Running   0  

六、Kibana对接Elasticsearch

6.1 Fluentd启动成功后,前往Kibana Dashboard

在浏览器中打开 http://<任意节点IP>:31966

建议选择edge这种自带翻译的浏览器,操作起来会方便一些

6.2 配置索引模式

Fluentd配置文件中采集的日志使用的是logstash格式 ,所以需要创建匹配logstash-*的索引模式来匹配Elasticsearch集群中的所有日志数据。

操作步骤

  1. 点击左侧的Discover
  1. 在文本框中输入 logstash-* 匹配索引,点击 Next step
  1. 选择 @timestamp 作为时间过滤器字段,点击 Create index pattern(创建索引模式)

6.3 查看日志

点击左侧的Discover,即可看到来自Kubernetes集群的所有日志数据。

七、测试:收集Pod容器日志

7.1 创建一个测试Pod

bash 复制代码
[root@hd1 ~]# vim pod.yaml
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']
bash 复制代码
[root@hd1 ~]# kubectl apply -f pod.yaml

7.2 在Kibana中查看和过滤Pod日志

登录Kibana控制面板,在Discover页面的搜索栏中输入 kubernetes.pod_name:counter,或者直接输入容器名counter,就能过滤名为counter的Pod的日志数据。

或者直接搜索节点名,就可以检索到所有hd1节点的日志:

八、总结

通过上面几个步骤,我们已经在Kubernetes集群成功部署了EFK日志系统,包括:

  • 3个Elasticsearch Pod(StatefulSet + 持久化存储)
  • 1个Kibana Pod(Deployment + NodePort Service)
  • 一组Fluentd Pod(DaemonSet,每个节点一个)

EFK系统的工作流程:

  1. Fluentd (DaemonSet)从每个节点的/var/log/var/lib/docker/containers目录采集日志
  2. Fluentd将日志添加Kubernetes元数据(Pod名称、Namespace、标签等)后,发送到Elasticsearch集群
  3. Elasticsearch对日志进行存储和索引
  4. Kibana提供Web界面供用户检索、分析和可视化日志数据

这套方案可以收集Kubernetes集群中所有节点上的容器日志和系统组件日志,并通过统一的Web界面进行查询和分析。

相关推荐
桑榆41617 小时前
双系统(Windows + Ubuntu)安装流程
linux·windows·ubuntu
张忠琳1 天前
【k3s】AutoK3s v0.9.3 Part 1 入口与 CLI 命令模块 — 超深度逐行分析之三
云原生·容器·kubernetes·k3s·autok3s
ltl1 天前
实时 OS 巡礼:VxWorks、QNX、Zephyr 与 PREEMPT_RT
linux
我是谁??1 天前
Ubuntu22.04更换清华源
linux·运维·服务器
Shell运维手记1 天前
Linux 常用基础命令学习笔记
linux·运维·笔记·学习·算法·github
m0_525724721 天前
AIOps全链路智能运维架构揭秘
云原生·devops
测试运维日常笔记1 天前
Linux系统安装配置TigerVNC服务器完整指南
linux·运维·服务器
雾时之林1 天前
Linux----cron定时服务
linux·运维·服务器
wengqidaifeng1 天前
1.从“会敲命令”到理解 Linux:15 个基础指令、文件树与命令执行的本质
linux·运维·服务器·ubuntu
xiaoxiangsiyan1 天前
企业日常运维高频应用服务全解
运维·网络·云原生·容器·dns