在现代 DevOps 实践中,日志管理和分析变得至关重要。尤其是在使用 Kubernetes 的动态环境中,分布式系统生成的日志数量庞大且复杂,传统的日志管理方式难以胜任。EFK 堆栈——由 Elasticsearch、Fluentd 和 Kibana 组成的强大组合,提供了一种高效的日志处理和可视化解决方案。

Elasticsearch 作为一种分布式搜索和分析引擎,能够快速存储、搜索和分析海量数据;Fluentd 是一个开源的数据收集器,用于统一记录、过滤和传输日志数据;而 Kibana 则提供了一个强大的用户界面,允许用户可视化和分析存储在 Elasticsearch 中的数据。这三者协同工作,可以帮助开发和运维团队轻松地收集、处理和可视化 Kubernetes 集群中的日志信息,从而提高系统的可观测性和故障诊断效率。

本篇文章将详细介绍如何在 Kubernetes 环境中部署和配置 EFK 堆栈,帮助您构建一个高效、可靠的日志管理系统。通过实际案例和步骤演示,您将学会如何使用 EFK 堆栈实现日志的集中收集、存储和可视化,从而提升应用程序的运维能力。

版本依赖关系

https://www.elastic.co/cn/support/matrix#matrix_compatibility

创建命名空间

编写资源清单文件

apiVersion: v1
kind: Namespace
metadata:
  name: log-efk

更新资源清单文件

kubectl apply -f namespace.yaml

查看命名空间

kubectl get ns

结果:

NAME              STATUS   AGE
default           Active   30d
kube-flannel      Active   30d
kube-node-lease   Active   30d
kube-public       Active   30d
kube-system       Active   30d
log-efk           Active   3s

创建 StorageClass 实现自动创建PV与PVC

编写资源清单文件

storageclass.yaml

# 创建用户ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: elasticsearch-storageclass-sa
  namespace: log-efk
---
# 创建ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: elasticsearch-storageclass-clusterrole
  namespace: log-efk
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "delete"]
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
  - 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: ["extensions"]
    resources: ["podsecuritypolicies"]
    verbs: ["use"]
    # 用于指定资源名称
    resourceNames: ["elasticsearch-storageclass-deployment-nfs-provisioner"]
---
# 创建ClusterRoleBinding绑定ClusterRole和ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: elasticsearch-storageclass-clusterrolebinding
  namespace: log-efk
# 用于指定的ServiceAccount
subjects:
  - kind: ServiceAccount
    name: elasticsearch-storageclass-sa
    namespace: log-efk
# 用于指定要绑定的 ClusterRole
roleRef:
  kind: ClusterRole
  name: elasticsearch-storageclass-clusterrole
  apiGroup: rbac.authorization.k8s.io
---
# 创建NFS供应商Pod
apiVersion: apps/v1
kind: Deployment
metadata:
  # Deployment的名称
  name: elasticsearch-storageclass-deployment-nfs-provisioner
  # Deployment的命名空间
  namespace: log-efk
spec:
  selector:
    # 标签选择器
    matchLabels:
      # 管理Pod的标签-与下方模板中的标签对应
      app: elasticsearch-storageclass-nfs-provisioner
  # 副本数量
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      # Pod的标签
      labels:
        app: elasticsearch-storageclass-nfs-provisioner
    spec:
      # 服务账号,使用上方创建的ServiceAccount,因为需要对PV进行操作,所以不能使用默认的ServiceAccount
      serviceAccountName: elasticsearch-storageclass-sa
      # 容器
      containers:
        - name: elasticsearch-storageclass-nfs-provisioner
          image: k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
          # 镜像拉取策略
          imagePullPolicy: IfNotPresent
          # 环境变量
          env:
            - name: PROVISIONER_NAME
              # 供应商的名称,需要于StorageClass中的provisioner的值一致
              value: elasticsearch-storageclass-nfs-provisioner
            - name: NFS_SERVER
              # NFS 服务器的主机名或 IP 地址
              value: 198.19.249.80
            - name: NFS_PATH
              # NFS 服务器导出的路径
              value: /data/nfs_pro
          # 容器的卷挂载
          volumeMounts:
            # 挂载到容器内部的目录
            - mountPath: /persistentvolumes
              # 卷的名称
              name: nfs-client-root
      # 容器的卷
      volumes:
        # 卷的名称。必须是 DNS_LABEL 并且在 Pod 内是唯一的
        - name: nfs-client-root
          nfs:
            # 服务器是 NFS 服务器的主机名或 IP 地址
            server: 198.19.249.80
            # NFS 服务器导出的路径
            path: /data/nfs_pro
---
# 创建StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  # StorageClass的名称,使用类型为StorageClass的卷时,需要指定StorageClass的名称
  name: elasticsearch-storageclass
  namespace: log-efk
# provisioner 是供应商的名称,需要与NFS供应商Pod中的PROVISIONER_NAME的值一致
provisioner: elasticsearch-storageclass-nfs-provisioner

逐段分析

  • 创建用户绑定权限

    创建用户绑定权限

    创建用户ServiceAccount

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: elasticsearch-storageclass-sa
      namespace: log-efk
    

    创建角色ClusterRole

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: elasticsearch-storageclass-clusterrole
      namespace: log-efk
    rules:
      - apiGroups: [""]
        resources: ["endpoints"]
        verbs: ["get", "list", "watch", "create", "update", "delete"]
      - apiGroups: [""]
        resources: ["nodes"]
        verbs: ["get", "list", "watch"]
      - 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: ["extensions"]
        resources: ["podsecuritypolicies"]
        verbs: ["use"]
        # 用于指定资源名称
        resourceNames: ["elasticsearch-storageclass-deployment-nfs-provisioner"]
    

    权限说明

    1. endpoints 资源的权限

      - apiGroups: [""]
        resources: ["endpoints"]
        verbs: ["get", "list", "watch", "create", "update", "delete"]
      
      • apiGroups: [""] 表示核心 API 组。
      • resources: ["endpoints"] 指定资源类型为 Endpoints。
      • verbs: ["get", "list", "watch", "create", "update", "delete"] 表示允许执行读取(get、list、watch)、创建、更新和删除操作。
    2. nodes 资源的权限

      - apiGroups: [""]
        resources: ["nodes"]
        verbs: ["get", "list", "watch"]
      
      • apiGroups: [""] 表示核心 API 组。
      • resources: ["nodes"] 指定资源类型为 Nodes。
      • verbs: ["get", "list", "watch"] 表示允许执行读取操作。
    3. persistentvolumes 资源的权限

      - apiGroups: [""]
        resources: ["persistentvolumes"]
        verbs: ["get", "list", "watch", "create", "delete"]
      
      • apiGroups: [""] 表示核心 API 组。
      • resources: ["persistentvolumes"] 指定资源类型为 PersistentVolumes。
      • verbs: ["get", "list", "watch", "create", "delete"] 表示允许执行读取、创建和删除操作。
    4. persistentvolumeclaims 资源的权限

      - apiGroups: [""]
        resources: ["persistentvolumeclaims"]
        verbs: ["get", "list", "watch", "update"]
      
      • apiGroups: [""] 表示核心 API 组。
      • resources: ["persistentvolumeclaims"] 指定资源类型为 PersistentVolumeClaims。
      • verbs: ["get", "list", "watch", "update"] 表示允许执行读取和更新操作。
    5. storageclasses 资源的权限

      - apiGroups: ["storage.k8s.io"]
        resources: ["storageclasses"]
        verbs: ["get", "list", "watch"]
      
      • apiGroups: ["storage.k8s.io"] 表示存储 API 组。
      • resources: ["storageclasses"] 指定资源类型为 StorageClasses。
      • verbs: ["get", "list", "watch"] 表示允许执行读取操作。
    6. events 资源的权限

      - apiGroups: [""]
        resources: ["events"]
        verbs: ["create", "update", "patch"]
      
      • apiGroups: [""] 表示核心 API 组。
      • resources: ["events"] 指定资源类型为 Events。
      • verbs: ["create", "update", "patch"] 表示允许执行创建、更新和部分更新(patch)操作。
    7. podsecuritypolicies 资源的权限

      - apiGroups: ["extensions"]
        resources: ["podsecuritypolicies"]
        verbs: ["use"]
        resourceNames: ["elasticsearch-storageclass-deployment-nfs-provisioner"]
      
      • apiGroups: ["extensions"] 表示扩展 API 组。
      • resources: ["podsecuritypolicies"] 指定资源类型为 PodSecurityPolicies。
      • verbs: ["use"] 表示允许使用指定的 PodSecurityPolicy。
      • resourceNames: ["elasticsearch-storageclass-deployment-nfs-provisioner"] 限制只能使用名为 elasticsearch-storageclass-deployment-nfs-provisioner 的 PodSecurityPolicy。

    创建ClusterRoleBinding绑定ClusterRole和ServiceAccount

    # 创建ClusterRoleBinding绑定ClusterRole和ServiceAccount
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: elasticsearch-storageclass-clusterrolebinding
      namespace: log-efk
    # 用于指定的ServiceAccount
    subjects:
      - kind: ServiceAccount
        name: elasticsearch-storageclass-sa
        namespace: log-efk
    # 用于指定要绑定的 ClusterRole
    roleRef:
      kind: ClusterRole
      name: elasticsearch-storageclass-clusterrole
      apiGroup: rbac.authorization.k8s.io
    

    创建用户 创建角色,将用户与角色绑定,角色权限为 操作 pv和pvc的全部操作

  • 创建NFS供应商驱动

    创建NFS供应商驱动

    创建nfs供应商驱动,可以通过驱动程序在NFS中自动的创建pv与pvc

    # 创建NFS供应商Pod
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      # Deployment的名称
      name: elasticsearch-storageclass-deployment-nfs-provisioner
      # Deployment的命名空间
      namespace: log-efk
    spec:
      selector:
        # 标签选择器
        matchLabels:
          # 管理Pod的标签-与下方模板中的标签对应
          app: elasticsearch-storageclass-nfs-provisioner
      # 副本数量
      replicas: 1
      strategy:
        type: Recreate
      template:
        metadata:
          # Pod的标签
          labels:
            app: elasticsearch-storageclass-nfs-provisioner
        spec:
          # 服务账号,使用上方创建的ServiceAccount,因为需要对PV进行操作,所以不能使用默认的ServiceAccount
          serviceAccountName: elasticsearch-storageclass-sa
          # 容器
          containers:
            - name: elasticsearch-storageclass-nfs-provisioner
              image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
              # 镜像拉取策略
              imagePullPolicy: IfNotPresent
              # 环境变量
              env:
                - name: PROVISIONER_NAME
                  # 供应商的名称,需要于StorageClass中的provisioner的值一致
                  value: elasticsearch-storageclass-nfs-provisioner
                - name: NFS_SERVER
                  # NFS 服务器的主机名或 IP 地址
                  value: 198.19.249.80
                - name: NFS_PATH
                  # NFS 服务器导出的路径
                  value: /data/nfs_pro
              # 容器的卷挂载
              volumeMounts:
                # 挂载到容器内部的目录
                - mountPath: /persistentvolumes
                  # 卷的名称
                  name: nfs-client-root
          # 容器的卷
          volumes:
            # 卷的名称。必须是 DNS_LABEL 并且在 Pod 内是唯一的
            - name: nfs-client-root
              nfs:
                # 服务器是 NFS 服务器的主机名或 IP 地址
                server: 198.19.249.80
                # NFS 服务器导出的路径
                path: /data/nfs_pro
    

    主意事项

    1. 创建NFS驱动程序,使用的是 上方创建的名称为 elasticsearch-storageclass-sa 的用户,用户绑定了 操作PV和PVC的全部角色权限,因为需要对PV和PVC进行操作,所以不能使用默认的ServiceAccount
    2. 环境变量中的 PROVISIONER_NAME 供应商名称,需要与下方的StorageClass中的provisioner的值一致
  • 创建StorageClass 绑定NFS驱动程序

    创建StorageClass 绑定NFS驱动程序

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      # StorageClass的名称,使用类型为StorageClass的卷时,需要指定StorageClass的名称
      name: elasticsearch-storageclass
      namespace: log-efk
    # provisioner 是供应商的名称,需要与NFS供应商Pod中的PROVISIONER_NAME的值一致
    provisioner: elasticsearch-storageclass-nfs-provisioner
    

    主意:
    创建StorageClass,provisioner供应商名称需要与NFS供应商Pod中的PROVISIONER_NAME的值一致

更新资源清单文件

kubectl apply -f storageclass.yaml

查看Pod状态

kubectl get pods -n log-efk

结果:

NAME                                                              READY   STATUS    RESTARTS   AGE
elasticsearch-storageclass-deployment-nfs-provisioner-548f8mhgn   1/1     Running   0          3m34s

创建elasticsearch

编写资源清单文件

elasticsearch.yaml

# 创建service服务 创建一个Headless Service,这样可以通过DNS直接访问到Pod
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch-service
  # 命名空间
  namespace: log-efk
spec:
  # 标签选择器,要代理的Pod,要与下文的模板中的标签匹配
  selector:
    app: elasticsearch
  # 服务的IP地址
  clusterIP: None
  ports:
    - port: 9200
      # 目标 Pod 上要访问的端口号或名称
      name: rest
    - port: 9300
      # 目标 Pod 上要访问的端口号或名称
      name: inter-node
---
# 编写 elasticsearch集群,创建一个StatefulSet,一个有标识的Pod集合,可以保证Pod的唯一性
kind: StatefulSet
apiVersion: apps/v1
metadata:
  name: elasticsearch
  namespace: log-efk
  labels:
    app: elasticsearch
spec:
  # StatefulSet 的服务的名称 负责该集的网络标识,需要与上文的service的名称一致
  serviceName: elasticsearch-service
  # 副本数量
  replicas: 3
  selector:
    # 标签选择器,要管理的Pod,要与下文的模板中的标签匹配
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      # Pod的标签
      labels:
        app: elasticsearch
    spec:
      # 容器
      containers:
        - name: elasticsearch
          image: docker.elastic.co/elasticsearch/elasticsearch:8.13.3
          imagePullPolicy: IfNotPresent
          # 资源限制
          resources:
            # 允许的最大计算资源量
            limits:
              cpu: 1000m
              memory: 2Gi
            # 允许的最小计算资源量
            requests:
              cpu: 100m
              memory: 1Gi
          # 端口
          ports:
            - containerPort: 9200
              # Pod 中的每个命名端口都必须具有唯一的名称。服务可以引用的端口名称
              name: rest
            - containerPort: 9300
              # Pod 中的每个命名端口都必须具有唯一的名称。服务可以引用的端口名称
              name: inter-node
          # 挂载卷
          volumeMounts:
            - name: elasticsearch-volume
              # 挂载路径
              mountPath: /usr/share/elasticsearch/data
          # 环境变量
          env:
            # 集群名称
            - name: cluster.name
              value: "elasticsearch"
            # 节点名称
            - name: node.name
              # 选自上文 metadata.name 的值
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            # 发现种子主机
            - name: discovery.seed_hosts
              value: "elasticsearch-0.elasticsearch-service.log-efk.svc.cluster.local,elasticsearch-1.elasticsearch-service.log-efk.svc.cluster.local,elasticsearch-2.elasticsearch-service.log-efk.svc.cluster.local"
            # 集群初始主节点
            - name: cluster.initial_master_nodes
              value: "elasticsearch-0,elasticsearch-1,elasticsearch-2"
            # Java虚拟机参数
            - name: ES_JAVA_OPTS
              value: "-Xms512m -Xmx512m"
            # 禁用xpack,关闭安全检查
            - name: xpack.security.enabled
              value: "false"
      # 初始化容器
      initContainers:
        # 用于修改目录权限
        - name: fix-permissions
          image: docker.io/library/busybox
          imagePullPolicy: IfNotPresent
          # 执行的命令 修改目录权限
          command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
          securityContext:
            # 特权模式是否开启 true开启 false关闭
            privileged: true
          # 挂载卷
          # 在初始化容器中的挂载卷会在主容器中也会挂载
          volumeMounts:
            - name: elasticsearch-volume
              mountPath: /usr/share/elasticsearch/data
        # 用于修改内核参数
        - name: init-sysctl
          image: docker.io/library/busybox
          imagePullPolicy: IfNotPresent
          command: ["sysctl", "-w", "vm.max_map_count=262144"]
          securityContext:
            privileged: true
        # 用于修改文件描述符的数量
        - name: increase-fd-ulimit
          image: docker.io/library/busybox
          imagePullPolicy: IfNotPresent
          # 执行的命令 修改文件描述符的数量
          command: ["sh", "-c", "ulimit -n 65536"]
          securityContext:
            privileged: true
  # 卷
  volumeClaimTemplates:
    - metadata:
        # 卷的名称
        name: elasticsearch-volume
      # 卷的规格
      spec:
        # 访问模式
        accessModes: ["ReadWriteOnce"]
        # 存储类
        storageClassName: elasticsearch-storageclass
        # 资源限制
        resources:
          requests:
            storage: 10Gi

逐段分析

  • 创建 Service

    创建无头服务 Headless Service

    # 创建service服务 创建一个Headless Service,这样可以通过DNS直接访问到Pod
    apiVersion: v1
    kind: Service
    metadata:
      name: elasticsearch-service
      # 命名空间
      namespace: log-efk
    spec:
      # 标签选择器,要代理的Pod,要与下文的模板中的标签匹配
      selector:
        app: elasticsearch
      # 服务的IP地址
      clusterIP: None
      ports:
        - port: 9200
          # 目标 Pod 上要访问的端口号或名称
          name: rest
        - port: 9300
          # 目标 Pod 上要访问的端口号或名称
          name: inter-node
    
    

    注意:

    1. StatefulSet中的serviceName需要与上方 Headless Service 的name保持一致,才可以正常通过DNS轮询访问
    2. 无头服务 通过 与 StatefulSet 配合 ,可以创建DNS访问,实现
      <statefulset-name>-<ordinal>.<service-name>.<namespace>.svc.cluster.local
      访问指定序号的Pod
      也可以通过
      <statefulset-serviceName>.<namespace>.svc.cluster.local
      轮询访问Pod
  • 创建 elasticsearch集群

    定义基本Pod信息

    # 编写 elasticsearch集群,创建一个StatefulSet,一个有标识的Pod集合,可以保证Pod的唯一性
    kind: StatefulSet
    apiVersion: apps/v1
    metadata:
      name: elasticsearch
      namespace: log-efk
      labels:
        app: elasticsearch
    spec:
      # StatefulSet 的服务的名称 负责该集的网络标识,需要与上文的service的名称一致
      serviceName: elasticsearch-service
      # 副本数量
      replicas: 3
      selector:
        # 标签选择器,要管理的Pod,要与下文的模板中的标签匹配
        matchLabels:
          app: elasticsearch
      template:
        metadata:
          # Pod的标签
          labels:
            app: elasticsearch
        spec:
          # 容器
          containers:
            - name: elasticsearch
              image: docker.elastic.co/elasticsearch/elasticsearch:8.13.3
              imagePullPolicy: IfNotPresent
              # 资源限制
              resources:
                # 允许的最大计算资源量
                limits:
                  cpu: 1000m
                  memory: 2Gi
                # 允许的最小计算资源量
                requests:
                  cpu: 100m
                  memory: 1Gi
              # 端口
              ports:
                - containerPort: 9200
                  # Pod 中的每个命名端口都必须具有唯一的名称。服务可以引用的端口名称
                  name: rest
                - containerPort: 9300
                  # Pod 中的每个命名端口都必须具有唯一的名称。服务可以引用的端口名称
                  name: inter-node
              # 挂载卷
              volumeMounts:
                - name: elasticsearch-volume
                  # 挂载路径
                  mountPath: /usr/share/elasticsearch/data
              # 环境变量
              env:
                # 集群名称
                - name: cluster.name
                  value: "elasticsearch"
                # 节点名称
                - name: node.name
                  # 选自上文 metadata.name 的值
                  valueFrom:
                    fieldRef:
                      fieldPath: metadata.name
                # 发现种子主机
                - name: discovery.seed_hosts
                  value: "elasticsearch-0.elasticsearch-service.log-efk.svc.cluster.local,elasticsearch-1.elasticsearch-service.log-efk.svc.cluster.local,elasticsearch-2.elasticsearch-service.log-efk.svc.cluster.local"
                # 集群初始主节点
                - name: cluster.initial_master_nodes
                  value: "elasticsearch-0,elasticsearch-1,elasticsearch-2"
                # Java虚拟机参数
                - name: ES_JAVA_OPTS
                  value: "-Xms512m -Xmx512m"
                # 禁用xpack,关闭安全检查
                - name: xpack.security.enabled
                  value: "false"
    

    定义了StatefulSet资源,一个有标识的Pod集合

    1. 暴露了两个端口号9200/9300,并定义了端口名称,端口名称在上方的service中有使用

    2. 定义了挂载卷,卷的名称与下方的volumeClaimTemplates的名称保持一致

    3. 定义了环境变量

      1. 定义集群的名称

      2. 定义了节点名称,节点引用上文的metadata.name

        将解析为 es-cluster-[0,1,2],取决于节点的指定顺序。

      3. 定义引用集群的节点地址

        discovery.seed_hosts:此字段用于设置在 Elasticsearch 集群中节点相互连接的发现方法,它为我们的集群指定了一个静态主机列表。由于我们之前配置的是无头服务,我们的 Pod 具有唯一的 DNS地址 elasticsearch-[0,1,2].elasticsearch-service.kube-logging.svc.cluster.local,因此我们相应地设置此地址变量即可。由于都在同一个 namespace 下面,所以我们可以将其缩短为 elasticsearch-[0,1,2].elasticsearch-service。

        要了解有关 Elasticsearch 发现的更多信息,请参阅 Elasticsearch 官方文档:
        https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-discovery.html。

      4. 定义集群初始化的主节点

      5. 定义了java虚拟机参数

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

      6. 禁用xpack,关闭安全检查

      定义初始化容器

            # 初始化容器
            initContainers:
              # 用于修改目录权限
              - name: fix-permissions
                image: docker.io/library/busybox
                imagePullPolicy: IfNotPresent
                # 执行的命令 修改目录权限
                command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
                securityContext:
                  # 特权模式是否开启 true开启 false关闭
                  privileged: true
                # 挂载卷
                # 在初始化容器中的挂载卷会在主容器中也会挂载
                volumeMounts:
                  - name: elasticsearch-volume
                    mountPath: /usr/share/elasticsearch/data
              # 用于修改内核参数
              - name: init-sysctl
                image: docker.io/library/busybox
                imagePullPolicy: IfNotPresent
                command: ["sysctl", "-w", "vm.max_map_count=262144"]
                securityContext:
                  privileged: true
              # 用于修改文件描述符的数量
              - name: increase-fd-ulimit
                image: docker.io/library/busybox
                imagePullPolicy: IfNotPresent
                # 执行的命令 修改文件描述符的数量
                command: ["sh", "-c", "ulimit -n 65536"]
                securityContext:
                  privileged: true
      

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

      1. 修改目录权限

        名为 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

      2. 修改内核参数

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

      3. 修改文件描述符的数量

        最后一个初始化容器是用来执行 ulimit 命令增加打开文件描述符的最大数量的。
        此外 Elastisearch Notes for Production Use 文档还提到了由于性能原因最好禁用 swap,对于 Kubernetes 集群而言,最好也是禁用 swap 分区的。

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

      定义容器卷的模板

        # 卷
        volumeClaimTemplates:
          - metadata:
              # 卷的名称
              name: elasticsearch-volume
            # 卷的规格
            spec:
              # 访问模式
              accessModes: ["ReadWriteOnce"]
              # 存储类
              storageClassName: elasticsearch-storageclass
              # 资源限制
              resources:
                requests:
                  storage: 10Gi
      

      我们这里使用 volumeClaimTemplates 来定义持久化模板,

      1. Kubernetes 会使用它为 Pod 创建PersistentVolume,设置访问模式为 ReadWriteOnce,这意味着它只能被 mount 到单个节点上进行读写,
      2. 使用了一个名为 elasticsearch-storageclass 的 StorageClass 对象,这里的storageClassName的名字就是上面创建动态存储类StorageClass的名称, 所以我们需要提前创建该对象,我们这里使用的 NFS 作为存储后端,所以需要安装一个对应的 nfs provisioner 驱动。

更新资源清单文件

kubectl apply -f elasticsearch.yaml

查看资源部署情况

查看Pod

kubectl get pods -n log-efk

结果:

NAME                                                              READY   STATUS    RESTARTS   AGE
elasticsearch-0                                                   1/1     Running   0          6m30s
elasticsearch-1                                                   1/1     Running   0          6m26s
elasticsearch-2                                                   1/1     Running   0          6m22s
elasticsearch-storageclass-deployment-nfs-provisioner-64d5c8qgb   1/1     Running   0          28m

查看Service

kubectl get svc

结果:

NAME                  TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes            ClusterIP   10.96.0.1    <none>        443/TCP   31d
statefulset-service   ClusterIP   None         <none>        80/TCP    12d

查看PV与PVC

  1. pvc

    kubectl get pvc -n log-efk
    

    结果:

    NAME                                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS                 VOLUMEATTRIBUTESCLASS   AGE
    elasticsearch-volume-elasticsearch-0   Bound    pvc-42cd36df-cc73-4cb9-bea6-c03931cfac90   10Gi       RWO            elasticsearch-storageclass   <unset>                 34m
    elasticsearch-volume-elasticsearch-1   Bound    pvc-abc069d4-f39f-4396-9114-5d00f48a170c   10Gi       RWO            elasticsearch-storageclass   <unset>                 34m
    elasticsearch-volume-elasticsearch-2   Bound    pvc-0fa0b7a9-660d-4b57-8318-0609a143b743   10Gi       RWO            elasticsearch-storageclass   <unset>                 34m
    
  2. pv

    kubectl get pv -n log-efk
    

    结果:

    NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                          STORAGECLASS                 VOLUMEATTRIBUTESCLASS   REASON   AGE
    pvc-0fa0b7a9-660d-4b57-8318-0609a143b743   10Gi       RWO            Delete           Bound    log-efk/elasticsearch-volume-elasticsearch-2   elasticsearch-storageclass   <unset>                          34m
    pvc-42cd36df-cc73-4cb9-bea6-c03931cfac90   10Gi       RWO            Delete           Bound    log-efk/elasticsearch-volume-elasticsearch-0   elasticsearch-storageclass   <unset>                          35m
    pvc-abc069d4-f39f-4396-9114-5d00f48a170c   10Gi       RWO            Delete           Bound    log-efk/elasticsearch-volume-elasticsearch-1   elasticsearch-storageclass   <unset>                          34m
    

校验elasticsearch接口是否可以使用

开放临时端口转发

kubectl port-forward elasticsearch-0  9200:9200 -n log-efk

访问测试

curl http://localhost:9200/_cluster/state?pretty

结果:

  },
  "routing_table" : {
    "indices" : { }
  },
  "routing_nodes" : {
    "unassigned" : [ ],
    "nodes" : {
      "VRbFlccpSAmm3LvZEYVRZA" : [ ],
      "uDgGXTKoRJin2OL_QyTV8A" : [ ],
      "FPeL_WuBQtm0Dk7apLcdLA" : [ ]
    }
  },
  "health" : {
    "disk" : {
      "high_watermark" : "90%",
      "high_max_headroom" : "150gb",
      "flood_stage_watermark" : "95%",
      "flood_stage_max_headroom" : "100gb",
      "frozen_flood_stage_watermark" : "95%",
      "frozen_flood_stage_max_headroom" : "20gb"
    },
    "shard_limits" : {
      "max_shards_per_node" : 1000,
      "max_shards_per_node_frozen" : 3000
    }
  }
}

创建Kibana可视化UI界面

编写配置文件

kibana-configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: kibana-config
  namespace: log-efk
data:
  kibana.yml: |
    server.name: kibana
    server.host: "0.0.0.0"
    elasticsearch.hosts: [ "http://elasticsearch-service.log-efk.svc.cluster.local:9200" ]
    i18n.locale: "zh-CN"

配置解析

  1. server.name:
    • 含义:设置 Kibana 服务器的名称。
    • 作用:这个名称可以在 Kibana 的用户界面上显示,帮助用户识别正在访问的 Kibana 实例。
    • 配置值kibana
    • 示例server.name: kibana
  2. server.host:
    • 含义:定义 Kibana 服务器监听的主机地址。
    • 作用"0.0.0.0" 表示 Kibana 将监听所有网络接口,这意味着它可以接受来自任何 IP 地址的连接。
    • 配置值"0.0.0.0"
    • 示例server.host: "0.0.0.0"
  3. elasticsearch.hosts:
    • 含义:指定 Elasticsearch 集群的地址。
    • 作用:配置 Kibana 用于连接的 Elasticsearch 实例或集群的 URL 列表。
    • 配置值[ "http://elasticsearch-service.log-efk.svc.cluster.local:9200" ]
      • http://elasticsearch-service.log-efk.svc.cluster.local:9200:这是在 Kubernetes 集群中 Elasticsearch 服务的地址和端口。elasticsearch-service 是 Elasticsearch 服务的名称,log-efk 是命名空间,:9200 是端口号。
    • 示例elasticsearch.hosts: [ "http://elasticsearch-service.log-efk.svc.cluster.local:9200" ]
  4. i18n.locale:
    • 含义:设置 Kibana 的语言环境(locale)。
    • 作用:将 Kibana 用户界面的语言设置为指定的语言和地区。这里设置为 "zh-CN",表示简体中文。
    • 配置值"zh-CN"
    • 示例i18n.locale: "zh-CN"

编写资源清单文件

kibana.yaml

apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: log-efk
spec:
  # 标签选择器,选择要公开的Pod
  selector:
    app: kibana
  #  服务的公开方式 NodePort为每个节点创建一个公开端口
  type: NodePort
  ports:
    # 此服务内部访问的端口
    - port: 5601
      # 此服务将转发到的端口
      targetPort: 5601
      # 此服务将公开的节点端口
      nodePort: 31601
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana-deployment
  namespace: log-efk
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      containers:
        - name: kibana
          image: docker.elastic.co/kibana/kibana:8.13.3
          imagePullPolicy: IfNotPresent
          # 端口
          ports:
            - containerPort: 5601
          # 资源限制
          resources:
            # 最小计算资源量
            requests:
              cpu: 100m
              memory: 100Mi
            # 最大计算资源量
            limits:
              cpu: 1000m
              memory: 1Gi
          # 环境变量
          # env:
          #   # 指向Elasticsearch服务,如果有configMap则不需要
          #   - name: ELASTICSEARCH_HOSTS
          #     value: http://elasticsearch-service.log-efk.svc.cluster.local:9200
          # 挂载的目录
          volumeMounts:
            - mountPath: /usr/share/kibana/config/kibana.yml
              name: kibana-config
              subPath: kibana.yml
      volumes:
        - name: kibana-config
          configMap:
            name: kibana-config

资源清单文件解释:

  1. 创建service,用于暴露pod容器接口,进行外部访问
  2. 创建 Deployment 控制器管理Pod
  3. 挂载配置文件

更新资源清单文件

kubectl apply -f .

查看kibana状态

kubectl get pods -n log-efk

结果:

NAME                                                              READY   STATUS    RESTARTS       AGE
elasticsearch-0                                                   1/1     Running   0              66m
elasticsearch-1                                                   1/1     Running   0              66m
elasticsearch-storageclass-deployment-nfs-provisioner-64d5c8qgb   1/1     Running   8 (101m ago)   42h
fluentd-ds-94bpq                                                  1/1     Running   0              11m
fluentd-ds-bkfhr                                                  1/1     Running   0              11m
fluentd-ds-w6ndn                                                  1/1     Running   0              11m
kibana-deployment-577d9d88b4-tffjp                                1/1     Running   0              2m14s

创建fluentd收集宿主机产生的日志

编写配置文件

apiVersion: v1
kind: ConfigMap
metadata:
  name: fulentd-config
  namespace: log-efk
  labels:
    app: fulentd-config
data:
  fluent.conf: |
    <source>
     @type tail
     path /var/log/*.log
     pos_file /var/log/fluentd-containers.log.pos
     tag kubernetes.*
     <parse>
       @type none
       time_key time
       time_format %Y-%m-%dT%H:%M:%S.%N%z
     </parse>
    </source>
    <source>
      @type tail
      path /var/log/containers/*.log
      pos_file /var/log/containers/fluentd.pos
      tag kubernetes.*
      <parse>
        @type none
      </parse>
    </source>

    <filter kubernetes.**>
     @type kubernetes_metadata
    </filter>

    <match **>
     @type elasticsearch
     @id elasticsearch
     host elasticsearch-service.log-efk.svc.cluster.local
     port 9200
     scheme http
     logstash_format true
     logstash_prefix fluentd
     logstash_dateformat %Y.%m.%d
     include_tag_key true
     tag_key @log_name
     index_name fluentd
     type_name _doc
     request_timeout 5s
     reconnect_on_error true
     reload_connections true
     reload_after 200
     reload_on_failure true
     <buffer>
       @type file
       path /var/log/fluentd-buffers/
       flush_interval 5s
       retry_forever true
       retry_max_interval 30
       chunk_limit_size 2M
       queue_limit_length 256
     </buffer>
    </match>
  parsers.conf: |
    <label @FLUENT_LOG>
      <match **>
        @type stdout
      </match>
    </label>

配置详情解析

Source 部分

  1. 第一个 source 配置

    <source>
      @type tail
      path /var/log/*.log
      pos_file /var/log/fluentd-containers.log.pos
      tag kubernetes.*
      <parse>
        @type none
        time_key time
        time_format %Y-%m-%dT%H:%M:%S.%N%z
      </parse>
    </source>
    
    • @type tail:使用 tail 插件从指定的文件中读取日志。
    • path /var/log/*.log:读取 /var/log 目录下所有以 .log 结尾的文件。
    • pos_file /var/log/fluentd-containers.log.pos:记录读取位置的文件,防止 Fluentd 重启后重复读取相同的日志。
    • tag kubernetes.*:为这类日志设置标签 kubernetes.*
    • <parse>:定义解析配置。
      • @type none:不进行日志解析,直接使用原始日志数据。
      • time_key time:指定时间戳的字段名称。
      • time_format %Y-%m-%dT%H:%M:%S.%N%z:时间戳的格式。
  2. 第二个 source 配置

    <source>
      @type tail
      path /var/log/containers/*.log
      pos_file /var/log/containers/fluentd.pos
      tag kubernetes.*
      <parse>
        @type none
      </parse>
    </source>
    
    • @type tail:使用 tail 插件从指定的文件中读取日志。
    • path /var/log/containers/*.log:读取 /var/log/containers 目录下所有以 .log 结尾的文件。
    • pos_file /var/log/containers/fluentd.pos:记录读取位置的文件,防止 Fluentd 重启后重复读取相同的日志。
    • tag kubernetes.*:为这类日志设置标签 kubernetes.*
    • <parse>:定义解析配置。
      • @type none:不进行日志解析,直接使用原始日志数据。

Filter 部分

<filter kubernetes.**>
  @type kubernetes_metadata
</filter>
  • <filter kubernetes.**>:过滤标签匹配 kubernetes.** 的日志。
  • @type kubernetes_metadata:使用 kubernetes_metadata 插件,自动添加 Kubernetes 的元数据(如 pod 名称、命名空间、容器名称等)到日志记录中。

Match 部分

<match **>
  @type elasticsearch
  @id elasticsearch
  host elasticsearch-service.log-efk.svc.cluster.local
  port 9200
  scheme http
  logstash_format true
  logstash_prefix fluentd
  logstash_dateformat %Y.%m.%d
  include_tag_key true
  tag_key @log_name
  index_name fluentd
  type_name _doc
  request_timeout 5s
  reconnect_on_error true
  reload_connections true
  reload_after 200
  reload_on_failure true
  <buffer>
    @type file
    path /var/log/fluentd-buffers/
    flush_interval 5s
    retry_forever true
    retry_max_interval 30
    chunk_limit_size 2M
    queue_limit_length 256
  </buffer>
</match>

  • <match **>:匹配所有日志。
  • @type elasticsearch:使用 Elasticsearch 插件,将日志发送到 Elasticsearch。
  • @id elasticsearch:配置 ID,唯一标识此插件实例。
  • host elasticsearch-service.log-efk.svc.cluster.local:Elasticsearch 服务的主机名。
  • port 9200:Elasticsearch 服务的端口。
  • scheme http:使用 HTTP 协议连接 Elasticsearch。
  • logstash_format true:以 Logstash 格式索引日志数据。
  • logstash_prefix fluentd:日志索引的前缀。
  • logstash_dateformat %Y.%m.%d:日志索引的日期格式。
  • include_tag_key true:在日志记录中包含标签键。
  • tag_key @log_name:标签键的名称。
  • index_name fluentd:Elasticsearch 索引名称。
  • type_name _doc:Elasticsearch 文档类型(7.x 版本后不再需要)。
  • request_timeout 5s:请求超时时间。
  • reconnect_on_error true:发生错误时重新连接。
  • reload_connections true:自动重载连接。
  • reload_after 200:每 200 个请求重载连接。
  • reload_on_failure true:连接失败时重载。
  • <buffer>:缓冲配置。
    • @type file:使用文件进行缓冲。
    • path /var/log/fluentd-buffers/:缓冲文件路径。
    • flush_interval 5s:缓冲刷新间隔。
    • retry_forever true:无限重试。
    • retry_max_interval 30:最大重试间隔。
    • chunk_limit_size 2M:每个缓冲块的最大大小。
    • queue_limit_length 256:队列最大长度。

parsers.conf 部分

<label @FLUENT_LOG>
  <match **>
    @type stdout
  </match>
</label>

  • <label @FLUENT_LOG>:定义一个标签为 @FLUENT_LOG 的部分。
  • <match **>:匹配所有日志。
  • @type stdout:将日志输出到标准输出(控制台)。

编写资源清单文件

# 创建一个 用户
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd-sa
  namespace: log-efk
---
# 创建一个角色
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluentd-cr
  namespace: log-efk
# 角色的规则
rules:
  - apiGroups: [""]
    resources: ["pods", "namespaces"]
    verbs: ["get", "watch", "list"]
---
# 创建一个 ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: fluentd-crb
  namespace: log-efk
# 绑定的用户
subjects:
  - kind: ServiceAccount
    # 要绑定的用户的名称
    name: fluentd-sa
    namespace: log-efk
# 绑定的角色
roleRef:
  kind: ClusterRole
  # 要绑定的角色的名称
  name: fluentd-cr
  apiGroup: rbac.authorization.k8s.io
---
# 创建一个DaemonSet 确保在每一个节点上都有一个fluentd pod
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-ds
  namespace: log-efk
spec:
  selector:
    # 标签选择器,选择要管理的Pod,就是下文的模板中的标签
    matchLabels:
      app: fluentd
  template:
    metadata:
      # pod的标签
      labels:
        app: fluentd
    spec:
      # 定义ServiceAccount
      serviceAccountName: fluentd-sa
      # 污点容忍度
      # 允许在master节点上运行
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          effect: NoSchedule
      containers:
        - name: fluentd
          image: fluent/fluentd-kubernetes-daemonset:v1.16-debian-elasticsearch8-2
          imagePullPolicy: IfNotPresent
          # # 环境变量 如果配置了configmap 则不需要配置环境变量
          # env:
          #   # ELASTICSEARCH的地址
          #   - name: FLUENT_ELASTICSEARCH_HOST
          #     value: "elasticsearch-service.log-efk.svc.cluster.local"
          #   # ELASTICSEARCH的端口
          #   - name: FLUENT_ELASTICSEARCH_PORT
          #     value: "9200"
          #   # ELASTICSEARCH的请求方式
          #   - name: FLUENT_ELASTICSEARCH_SCHEME
          #     value: "http"
          #   # FLUENTD的日志级别
          #   # 由于 FLUENTD_SYSTEMD_CONF 被设置为 disable,Fluentd
          #   # 不会从 systemd 日志中收集日志,只会从配置的宿主机路径 /var/log 和 /var/lib/docker/containers 收集日志。
          #   - name: FLUENTD_SYSTEMD_CONF
          #     value: disable
          # 资源限制
          resources:
            # 最大资源限制
            limits:
              memory: 200Mi
            # 最小资源限制
            requests:
              cpu: 100m
              memory: 200Mi
          # 挂载的目录
          volumeMounts:
            - mountPath: /var/log
              name: log
            # 挂载配置文件
            - name: fulentd-config
              mountPath: /fluentd/etc/fluent.conf
              # 卷中的子路径,表示你希望挂载的卷中的特定文件或目录。
              subPath: fluent.conf
      # 容器终止的强制关闭时间
      terminationGracePeriodSeconds: 30
      # 定义卷
      volumes:
        - name: log
          # 宿主机路径
          hostPath:
            path: /var/log
        - name: fulentd-config
          configMap:
            name: fulentd-config

资源清单文件解释:

  1. 创建一个角色,基于查看pods与namespaces的权限
  2. 使用 DaemonSet 控制器,确保每一个节点上都有一个fluentd Pod
  3. 定义污点容忍度,确保可以在Master节点运行
  4. 挂载目录
    1. 将宿主机的/var/log挂载到容器中的/var/log
    2. 将配置文件挂载到容器中的配置位置

更新资源清单文件

kubectl apply -f .

查看fulentd资源情况

kubectl get pods -n log-efk

结果:

NAME                                                              READY   STATUS    RESTARTS       AGE
elasticsearch-0                                                   1/1     Running   0              66m
elasticsearch-1                                                   1/1     Running   0              66m
elasticsearch-storageclass-deployment-nfs-provisioner-64d5c8qgb   1/1     Running   8 (101m ago)   42h
fluentd-ds-94bpq                                                  1/1     Running   0              11m
fluentd-ds-bkfhr                                                  1/1     Running   0              11m
fluentd-ds-w6ndn                                                  1/1     Running   0              11m
kibana-deployment-577d9d88b4-tffjp                                1/1     Running   0              2m14s

创建测试Pod不断打印日志

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
    - name: count
      image: busybox
      imagePullPolicy: IfNotPresent
      # 创建一个无限循环,每秒钟打印一次当前的日期和时间,以及一个递增的计数器
      args: ["/bin/sh", "-c", 'i=0; while true; do echo "$i: $(date) hello fluentd"; i=$((i+1)); sleep 1; done']

创建一个pod不断打印日志

查看Kibana可视化UI界面中的日志

  1. 点击左边菜单,最下方Management中的Stack Management
    在这里插入图片描述

  2. 选择数据中的索引管理
    在这里插入图片描述

  3. 查看菜单中的fluentd收集的索引
    在这里插入图片描述

  4. 选择Kibana中的数据视图
    在这里插入图片描述

  5. 选择右上角创建视图
    在这里插入图片描述

  6. 创建对应的视图,链接对应的索引

  7. 点击保存数据视图

  8. 回到主页选择最上方的Discover
    在这里插入图片描述

  9. 右上角选择创建的视图
    在这里插入图片描述 11. 查看日志
    在这里插入图片描述

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐