如何在Kubernetes上设置Elasticsearch、Fluentd和Kibana(EFK)日志堆栈
介绍
在 Kubernetes 集群上运行多个服务和应用程序时,集中的集群级日志堆栈可以帮助您快速整理和分析 Pod 产生的大量日志数据。 一种流行的集中式日志记录解决方案是 Elasticsearch、Fluentd 和 Kibana (EFK) 堆栈。
Elasticsearch 是一个实时、分布式和可扩展的搜索引擎,它允许全文和结构化搜索以及分析。 它通常用于索引和搜索大量日志数据,但也可用于搜索许多不同类型的文档。
Elasticsearch 通常与 Kibana 一起部署,这是一个强大的 Elasticsearch 数据可视化前端和仪表板。 Kibana 允许您通过 Web 界面探索 Elasticsearch 日志数据,并构建仪表板和查询以快速回答问题并深入了解您的 Kubernetes 应用程序。
在本教程中,我们将使用 Fluentd 来收集、转换并将日志数据发送到 Elasticsearch 后端。 Fluentd 是一个流行的开源数据收集器,我们将在我们的 Kubernetes 节点上设置它来跟踪容器日志文件、过滤和转换日志数据,并将其传送到 Elasticsearch 集群,在那里它会被索引和存储。
我们将首先配置和启动一个可扩展的 Elasticsearch 集群,然后创建 Kibana Kubernetes 服务和部署。 最后,我们将 Fluentd 设置为 DaemonSet,以便它在每个 Kubernetes 工作节点上运行。
先决条件
在开始阅读本指南之前,请确保您已准备好以下内容:
- 启用了基于角色的访问控制 (RBAC) 的 Kubernetes 1.10+ 集群
- 确保您的集群有足够的资源来部署 EFK 堆栈,如果没有,请通过添加工作程序节点来扩展您的集群。 我们将部署一个 3-Pod Elasticsearch 集群(如有必要,您可以将其缩小到 1 个)以及一个 Kibana Pod。 每个工作节点也将运行一个 Fluentd Pod。 本指南中的集群由 3 个工作节点和一个托管控制平面组成。
- 安装在本地计算机上的
kubectl
命令行工具,配置为连接到您的集群。 您可以在官方文档中阅读更多关于安装kubectl
的信息。
设置好这些组件后,您就可以开始阅读本指南了。
第 1 步 — 创建命名空间
在推出 Elasticsearch 集群之前,我们将首先创建一个命名空间,我们将在其中安装所有日志记录工具。 Kubernetes 允许您使用称为命名空间的“虚拟集群”抽象来分离集群中运行的对象。 在本指南中,我们将创建一个 kube-logging
命名空间,我们将在其中安装 EFK 堆栈组件。 这个命名空间还将允许我们快速清理和删除日志堆栈,而不会丢失 Kubernetes 集群的任何功能。
首先,首先使用 kubectl
调查集群中现有的命名空间:
kubectl get namespaces
您应该会看到以下三个初始命名空间,它们预装在您的 Kubernetes 集群中:
OutputNAME STATUS AGE default Active 5m kube-system Active 5m kube-public Active 5m
default
命名空间包含在未指定命名空间的情况下创建的对象。 kube-system
命名空间包含 Kubernetes 系统创建和使用的对象,例如 kube-dns
、kube-proxy
和 kubernetes-dashboard
。 保持此命名空间干净且不被您的应用程序和检测工作负载污染是一种很好的做法。
kube-public
命名空间是另一个自动创建的命名空间,可用于存储您希望在整个集群中可读和可访问的对象,甚至对未经身份验证的用户也是如此。
要创建 kube-logging
命名空间,首先使用您喜欢的编辑器打开并编辑一个名为 kube-logging.yaml
的文件,例如 nano:
nano kube-logging.yaml
在您的编辑器中,粘贴以下命名空间对象 YAML:
kube-logging.yaml
kind: Namespace apiVersion: v1 metadata: name: kube-logging
然后,保存并关闭文件。
在这里,我们将 Kubernetes 对象的 kind
指定为 Namespace
对象。 要了解有关 Namespace
对象的更多信息,请参阅 Kubernetes 官方文档中的 Namespaces Walkthrough。 我们还指定用于创建对象的 Kubernetes API 版本(v1
),并给它一个 name
、kube-logging
。
创建 kube-logging.yaml
命名空间对象文件后,使用带有 -f
文件名标志的 kubectl create
创建命名空间:
kubectl create -f kube-logging.yaml
您应该看到以下输出:
Outputnamespace/kube-logging created
然后,您可以确认命名空间已成功创建:
kubectl get namespaces
此时,您应该会看到新的 kube-logging
命名空间:
OutputNAME STATUS AGE default Active 23m kube-logging Active 1m kube-public Active 23m kube-system Active 23m
我们现在可以将 Elasticsearch 集群部署到这个隔离的日志命名空间中。
第 2 步 — 创建 Elasticsearch StatefulSet
现在我们已经创建了一个命名空间来容纳我们的日志堆栈,我们可以开始推出它的各种组件。 我们将首先部署一个 3 节点的 Elasticsearch 集群。
在本指南中,我们使用 3 个 Elasticsearch Pod 来避免在高可用性、多节点集群中出现的“脑裂”问题。 在高层次上,“裂脑”是当一个或多个节点无法与其他节点通信时出现的,并且会选出几个“裂脑”主节点。 对于 3 个节点,如果一个节点暂时与集群断开连接,其他两个节点可以选举一个新的主节点,并且集群可以继续运行,同时最后一个节点尝试重新加入。 要了解更多信息,请参阅 Elasticsearch 中集群协调的新时代 和 投票配置。
创建无头服务
首先,我们将创建一个名为 elasticsearch
的无头 Kubernetes 服务,它将为 3 个 Pod 定义一个 DNS 域。 无头服务不执行负载平衡或具有静态 IP; 要了解更多关于无头服务的信息,请查阅官方 Kubernetes 文档。
使用您喜欢的编辑器打开一个名为 elasticsearch_svc.yaml
的文件:
nano elasticsearch_svc.yaml
粘贴以下 Kubernetes 服务 YAML:
elasticsearch_svc.yaml
kind: Service apiVersion: v1 metadata: name: elasticsearch namespace: kube-logging labels: app: elasticsearch spec: selector: app: elasticsearch clusterIP: None ports: - port: 9200 name: rest - port: 9300 name: inter-node
然后,保存并关闭文件。
我们在 kube-logging
命名空间中定义了一个名为 elasticsearch
的 Service
,并赋予它 app: elasticsearch
标签。 然后我们将 .spec.selector
设置为 app: elasticsearch
,以便服务选择带有 app: elasticsearch
标签的 Pod。 当我们将 Elasticsearch StatefulSet 与此 Service 关联时,Service 将返回指向带有 app: elasticsearch
标签的 Elasticsearch Pod 的 DNS A 记录。
然后我们设置 clusterIP: None
,使服务无头。 最后,我们定义端口 9200
和 9300
分别用于与 REST API 交互和节点间通信。
使用 kubectl
创建服务:
kubectl create -f elasticsearch_svc.yaml
您应该看到以下输出:
Outputservice/elasticsearch created
最后,使用 kubectl get
再次检查服务是否已成功创建:
kubectl get services --namespace=kube-logging
您应该看到以下内容:
OutputNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE elasticsearch ClusterIP None <none> 9200/TCP,9300/TCP 26s
现在我们已经为 Pod 设置了无头服务和稳定的 .elasticsearch.kube-logging.svc.cluster.local
域,我们可以继续创建 StatefulSet。
创建有状态集
Kubernetes StatefulSet 允许您为 Pod 分配一个稳定的身份并授予它们稳定、持久的存储。 Elasticsearch 需要稳定的存储来在 Pod 重新调度和重启期间持久保存数据。 要了解有关 StatefulSet 工作负载的更多信息,请参阅 Kubernetes 文档中的 Statefulsets 页面。
在您喜欢的编辑器中打开一个名为 elasticsearch_statefulset.yaml
的文件:
nano elasticsearch_statefulset.yaml
我们将逐节浏览 StatefulSet 对象定义,将块粘贴到此文件中。
首先粘贴以下块:
elasticsearch_statefulset.yaml
apiVersion: apps/v1 kind: StatefulSet metadata: name: es-cluster namespace: kube-logging spec: serviceName: elasticsearch replicas: 3 selector: matchLabels: app: elasticsearch template: metadata: labels: app: elasticsearch
在这个块中,我们在 kube-logging
命名空间中定义了一个名为 es-cluster
的 StatefulSet。 然后,我们使用 serviceName
字段将其与我们之前创建的 elasticsearch
服务相关联。 这确保了 StatefulSet 中的每个 Pod 都可以使用以下 DNS 地址访问:es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local
,其中 [0,1,2]
对应于 Pod 分配的整数序号。
我们指定 3 个 replicas
(Pods) 并将 matchLabels
选择器设置为 app: elasticseach
,然后我们将其镜像到 .spec.template.metadata
部分。 .spec.selector.matchLabels
和 .spec.template.metadata.labels
字段必须匹配。
我们现在可以继续讨论对象规范。 将以下 YAML 块粘贴到前一个块的正下方:
elasticsearch_statefulset.yaml
. . . spec: containers: - name: elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0 resources: limits: cpu: 1000m requests: cpu: 100m ports: - containerPort: 9200 name: rest protocol: TCP - containerPort: 9300 name: inter-node protocol: TCP volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data env: - name: cluster.name value: k8s-logs - name: node.name valueFrom: fieldRef: fieldPath: metadata.name - name: discovery.seed_hosts value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch" - name: cluster.initial_master_nodes value: "es-cluster-0,es-cluster-1,es-cluster-2" - name: ES_JAVA_OPTS value: "-Xms512m -Xmx512m"
这里我们在 StatefulSet 中定义 Pod。 我们将容器命名为 elasticsearch
并选择 docker.elastic.co/elasticsearch/elasticsearch:7.2.0
Docker 映像。 此时,您可以修改此镜像标签以对应您自己的内部 Elasticsearch 镜像,或不同的版本。 请注意,出于本指南的目的,仅 Elasticsearch 7.2.0
已经过测试。
然后我们使用 resources
字段来指定容器至少需要保证 0.1 个 vCPU,并且可以突发到 1 个 vCPU(这在执行初始大型摄取或处理负载时限制了 Pod 的资源使用长钉)。 您应该根据预期的负载和可用资源修改这些值。 要了解有关资源请求和限制的更多信息,请参阅官方 Kubernetes 文档。
然后,我们分别为 REST API 和节点间通信打开并命名端口 9200
和 9300
。 我们指定一个名为 data
的 volumeMount
,它将名为 data
的 PersistentVolume 挂载到路径 /usr/share/elasticsearch/data
的容器中。 我们将在后面的 YAML 块中为此 StatefulSet 定义 VolumeClaims。
最后,我们在容器中设置一些环境变量:
cluster.name
:Elasticsearch 集群的名称,在本指南中为k8s-logs
。node.name
:节点的名称,我们使用valueFrom
将其设置为.metadata.name
字段。 这将解析为es-cluster-[0,1,2]
,具体取决于节点的分配序号。discovery.seed_hosts
:此字段设置集群中的主合格节点列表,这些节点将为节点发现过程提供种子。 在本指南中,由于我们之前配置的无头服务,我们的 Pod 具有es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local
形式的域,因此我们相应地设置了这个变量。 使用本地命名空间 Kubernetes DNS 解析,我们可以将其缩短为es-cluster-[0,1,2].elasticsearch
。 要了解有关 Elasticsearch 发现的更多信息,请参阅官方 Elasticsearch 文档。cluster.initial_master_nodes
:该字段还指定了将参与主节点选举过程的主节点的列表。 请注意,对于此字段,您应该通过节点的node.name
而非主机名来识别节点。ES_JAVA_OPTS
:这里我们将其设置为-Xms512m -Xmx512m
,它告诉 JVM 使用 512 MB 的最小和最大堆大小。 您应该根据集群的资源可用性和需求调整这些参数。 要了解更多信息,请参阅设置堆大小。
我们将粘贴的下一个块如下所示:
elasticsearch_statefulset.yaml
. . . initContainers: - name: fix-permissions image: busybox command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"] securityContext: privileged: true volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data - name: increase-vm-max-map image: busybox command: ["sysctl", "-w", "vm.max_map_count=262144"] securityContext: privileged: true - name: increase-fd-ulimit image: busybox command: ["sh", "-c", "ulimit -n 65536"] securityContext: privileged: true
在这个块中,我们定义了几个在主 elasticsearch
应用程序容器之前运行的 Init 容器。 这些初始化容器每个都按照它们定义的顺序运行到完成。 要了解有关 Init Containers 的更多信息,请参阅官方 Kubernetes 文档。
第一个名为 fix-permissions
,运行 chown
命令将 Elasticsearch 数据目录的所有者和组更改为 Elasticsearch 用户的 UID 1000:1000
。 默认情况下,Kubernetes 将数据目录挂载为 root
,这使得 Elasticsearch 无法访问它。 要了解有关此步骤的更多信息,请参阅 Elasticsearch 的“Notes for production use and defaults”。
第二个,名为 increase-vm-max-map
,运行一个命令来增加操作系统对 mmap 计数的限制,默认情况下可能太低,导致内存不足错误。 要了解有关此步骤的更多信息,请参阅官方 Elasticsearch 文档。
下一个要运行的 Init Container 是 increase-fd-ulimit
,它运行 ulimit
命令来增加打开文件描述符的最大数量。 要了解有关此步骤的更多信息,请参阅 Elasticsearch 官方文档中的“生产使用和默认值说明”。
注意: Elasticsearch 生产使用注意事项 还提到出于性能原因禁用交换。 根据您的 Kubernetes 安装或提供程序,交换可能已被禁用。 要检查这一点,请将 exec
放入正在运行的容器中并运行 cat /proc/swaps
以列出活动的交换设备。 如果您在那里什么都看不到,则交换被禁用。
现在我们已经定义了我们的主应用程序容器和在它之前运行以调整容器操作系统的 Init 容器,我们可以将最后一部分添加到 StatefulSet 对象定义文件中:volumeClaimTemplates
。
粘贴到以下 volumeClaimTemplate
块中:
elasticsearch_statefulset.yaml
. . . volumeClaimTemplates: - metadata: name: data labels: app: elasticsearch spec: accessModes: [ "ReadWriteOnce" ] storageClassName: do-block-storage resources: requests: storage: 100Gi
在这个块中,我们定义了 StatefulSet 的 volumeClaimTemplates
。 Kubernetes 将使用它为 Pod 创建 PersistentVolume。 在上面的块中,我们将其命名为 data
(也就是我们在前面定义的 volumeMount
中所指的 name
),并赋予它相同的 [X142X ] 标签作为我们的 StatefulSet。
然后我们将其访问模式指定为ReadWriteOnce
,这意味着它只能被单个节点以读写方式挂载。 在本指南中,我们将存储类定义为 do-block-storage
,因为我们使用 DigitalOcean Kubernetes 集群进行演示。 您应该根据运行 Kubernetes 集群的位置更改此值。 要了解更多信息,请参阅 Persistent Volume 文档。
最后,我们指定我们希望每个 PersistentVolume 的大小为 100GiB。 您应该根据生产需要调整此值。
完整的 StatefulSet 规范应如下所示:
elasticsearch_statefulset.yaml
apiVersion: apps/v1 kind: StatefulSet metadata: name: es-cluster namespace: kube-logging spec: serviceName: elasticsearch replicas: 3 selector: matchLabels: app: elasticsearch template: metadata: labels: app: elasticsearch spec: containers: - name: elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0 resources: limits: cpu: 1000m requests: cpu: 100m ports: - containerPort: 9200 name: rest protocol: TCP - containerPort: 9300 name: inter-node protocol: TCP volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data env: - name: cluster.name value: k8s-logs - name: node.name valueFrom: fieldRef: fieldPath: metadata.name - name: discovery.seed_hosts value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch" - name: cluster.initial_master_nodes value: "es-cluster-0,es-cluster-1,es-cluster-2" - name: ES_JAVA_OPTS value: "-Xms512m -Xmx512m" initContainers: - name: fix-permissions image: busybox command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"] securityContext: privileged: true volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data - name: increase-vm-max-map image: busybox command: ["sysctl", "-w", "vm.max_map_count=262144"] securityContext: privileged: true - name: increase-fd-ulimit image: busybox command: ["sh", "-c", "ulimit -n 65536"] securityContext: privileged: true volumeClaimTemplates: - metadata: name: data labels: app: elasticsearch spec: accessModes: [ "ReadWriteOnce" ] storageClassName: do-block-storage resources: requests: storage: 100Gi
对 Elasticsearch 配置感到满意后,保存并关闭文件。
现在,使用 kubectl
部署 StatefulSet:
kubectl create -f elasticsearch_statefulset.yaml
您应该看到以下输出:
Outputstatefulset.apps/es-cluster created
您可以使用 kubectl rollout status
监控 StatefulSet:
kubectl rollout status sts/es-cluster --namespace=kube-logging
当集群推出时,您应该会看到以下输出:
OutputWaiting for 3 pods to be ready... Waiting for 2 pods to be ready... Waiting for 1 pods to be ready... partitioned roll out complete: 3 new pods have been updated...
部署完所有 Pod 后,您可以通过对 REST API 执行请求来检查您的 Elasticsearch 集群是否正常运行。
为此,首先使用 kubectl port-forward
将本地端口 9200
转发到 Elasticsearch 节点之一 (es-cluster-0
) 上的端口 9200
:
kubectl port-forward es-cluster-0 9200:9200 --namespace=kube-logging
然后,在单独的终端窗口中,对 REST API 执行 curl
请求:
curl http://localhost:9200/_cluster/state?pretty
您应该看到以下输出:
Output{ "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" : { } } }, ...
这表明我们的 Elasticsearch 集群 k8s-logs
已经成功创建了 3 个节点:es-cluster-0
、es-cluster-1
和 es-cluster-2
。 当前主节点为es-cluster-0
。
现在您的 Elasticsearch 集群已启动并运行,您可以继续为其设置 Kibana 前端。
第 3 步 — 创建 Kibana 部署和服务
要在 Kubernetes 上启动 Kibana,我们将创建一个名为 kibana
的服务,以及一个包含一个 Pod 副本的部署。 您可以根据生产需要扩展副本的数量,并且可以选择为服务指定 LoadBalancer
类型,以在 Deployment pod 之间负载平衡请求。
这一次,我们将在同一个文件中创建服务和部署。 在您喜欢的编辑器中打开一个名为 kibana.yaml
的文件:
nano kibana.yaml
粘贴以下服务规范:
kibana.yaml
apiVersion: v1 kind: Service metadata: name: kibana namespace: kube-logging labels: app: kibana spec: ports: - port: 5601 selector: app: kibana --- apiVersion: apps/v1 kind: Deployment metadata: name: kibana namespace: kube-logging labels: app: kibana spec: replicas: 1 selector: matchLabels: app: kibana template: metadata: labels: app: kibana spec: containers: - name: kibana image: docker.elastic.co/kibana/kibana:7.2.0 resources: limits: cpu: 1000m requests: cpu: 100m env: - name: ELASTICSEARCH_URL value: http://elasticsearch:9200 ports: - containerPort: 5601
然后,保存并关闭文件。
在本规范中,我们在 kube-logging
命名空间中定义了一个名为 kibana
的服务,并为其指定了 app: kibana
标签。
我们还指定它应该可以在端口 5601
上访问,并使用 app: kibana
标签来选择服务的目标 Pod。
在 Deployment
规范中,我们定义了一个名为 kibana
的部署,并指定我们想要 1 个 Pod 副本。
我们使用 docker.elastic.co/kibana/kibana:7.2.0
图像。 此时,您可以替换您自己的私有或公共 Kibana 镜像来使用。
我们指定我们希望向 Pod 保证至少 0.1 个 vCPU,最高达到 1 个 vCPU 的限制。 您可以根据预期的负载和可用资源更改这些参数。
接下来,我们使用 ELASTICSEARCH_URL
环境变量来设置 Elasticsearch 集群的端点和端口。 使用 Kubernetes DNS,此端点对应于其服务名称 elasticsearch
。 此域将解析为 3 个 Elasticsearch Pod 的 IP 地址列表。 要了解有关 Kubernetes DNS 的更多信息,请参阅 DNS for Services and Pods。
最后,我们将 Kibana 的容器端口设置为 5601
,kibana
服务会将请求转发到该端口。
一旦您对 Kibana 配置感到满意,您可以使用 kubectl
推出服务和部署:
kubectl create -f kibana.yaml
您应该看到以下输出:
Outputservice/kibana created deployment.apps/kibana created
您可以通过运行以下命令来检查部署是否成功:
kubectl rollout status deployment/kibana --namespace=kube-logging
您应该看到以下输出:
Outputdeployment "kibana" successfully rolled out
要访问 Kibana 接口,我们将再次将本地端口转发到运行 Kibana 的 Kubernetes 节点。 使用 kubectl get
获取 Kibana Pod 详细信息:
kubectl get pods --namespace=kube-logging
OutputNAME READY STATUS RESTARTS AGE es-cluster-0 1/1 Running 0 55m es-cluster-1 1/1 Running 0 54m es-cluster-2 1/1 Running 0 54m kibana-6c9fb4b5b7-plbg2 1/1 Running 0 4m27s
在这里,我们观察到我们的 Kibana Pod 被称为 kibana-6c9fb4b5b7-plbg2
。
将本地端口 5601
转发到此 Pod 上的端口 5601
:
kubectl port-forward kibana-6c9fb4b5b7-plbg2 5601:5601 --namespace=kube-logging
您应该看到以下输出:
OutputForwarding from 127.0.0.1:5601 -> 5601 Forwarding from [::1]:5601 -> 5601
现在,在您的网络浏览器中,访问以下 URL:
http://localhost:5601
如果您看到以下 Kibana 欢迎页面,则表明您已成功将 Kibana 部署到 Kubernetes 集群中:
您现在可以继续推出 EFK 堆栈的最后一个组件:日志收集器 Fluentd。
第四步——创建 Fluentd DaemonSet
在本指南中,我们将 Fluentd 设置为 DaemonSet,这是一种 Kubernetes 工作负载类型,它在 Kubernetes 集群中的每个节点上运行给定 Pod 的副本。 使用这个 DaemonSet 控制器,我们将在集群中的每个节点上推出 Fluentd 日志代理 Pod。 要了解有关此日志记录架构的更多信息,请参阅 Kubernetes 官方文档中的“使用节点日志记录代理 ”。
在 Kubernetes 中,记录到 stdout
和 stderr
的容器化应用程序的日志流被捕获并重定向到节点上的 JSON 文件。 Fluentd Pod 将跟踪这些日志文件、过滤日志事件、转换日志数据,并将其发送到我们在 步骤 2 中部署的 Elasticsearch 日志记录后端。
除了容器日志,Fluentd 代理还会跟踪 Kubernetes 系统组件日志,如 kubelet、kube-proxy 和 Docker 日志。 要查看 Fluentd 日志代理跟踪的完整源列表,请参阅用于配置日志代理的 kubernetes.conf 文件。 要了解有关登录 Kubernetes 集群的更多信息,请参阅 Kubernetes 官方文档中的“节点级别的日志记录”。
首先在您喜欢的文本编辑器中打开一个名为 fluentd.yaml
的文件:
nano fluentd.yaml
再一次,我们将逐块粘贴 Kubernetes 对象定义,并在进行过程中提供上下文。 在本指南中,我们使用 Fluentd 维护者提供的 Fluentd DaemonSet 规范 。 Fluentd 维护者提供的另一个有用资源是 Kuberentes Fluentd。
首先,粘贴以下 ServiceAccount 定义:
流利的.yaml
apiVersion: v1 kind: ServiceAccount metadata: name: fluentd namespace: kube-logging labels: app: fluentd
在这里,我们创建了一个名为 fluentd
的服务帐户,Fluentd Pod 将使用它来访问 Kubernetes API。 我们在 kube-logging
命名空间中创建它,并再次为其赋予标签 app: fluentd
。 要了解更多关于 Kubernetes 中的 Service Accounts 的信息,请参阅 Kubernetes 官方文档中的 Configure Service Accounts for Pods。
接下来,粘贴以下 ClusterRole
块:
流利的.yaml
. . . --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: fluentd labels: app: fluentd rules: - apiGroups: - "" resources: - pods - namespaces verbs: - get - list - watch
这里我们定义了一个名为 fluentd
的 ClusterRole,我们向它授予 get
、list
和 watch
对 pods
和 namespaces
对象。 ClusterRoles 允许您授予对集群范围的 Kubernetes 资源(如节点)的访问权限。 要了解有关基于角色的访问控制和集群角色的更多信息,请参阅 Kubernetes 官方文档中的 Using RBAC Authorization。
现在,粘贴以下 ClusterRoleBinding
块:
流利的.yaml
. . . --- 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
在这个块中,我们定义了一个名为 fluentd
的 ClusterRoleBinding
,它将 fluentd
ClusterRole 绑定到 fluentd
服务帐户。 这将授予 fluentd
ServiceAccount fluentd
集群角色中列出的权限。
此时我们可以开始粘贴实际的 DaemonSet 规范:
流利的.yaml
. . . --- apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd namespace: kube-logging labels: app: fluentd
在这里,我们在 kube-logging
命名空间中定义了一个名为 fluentd
的 DaemonSet,并赋予它 app: fluentd
标签。
接下来,粘贴到以下部分:
流利的.yaml
. . . spec: selector: matchLabels: app: fluentd template: metadata: labels: app: fluentd spec: serviceAccount: fluentd serviceAccountName: fluentd tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: fluentd image: fluent/fluentd-kubernetes-daemonset:v1.4.2-debian-elasticsearch-1.1 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
在这里,我们匹配 .metadata.labels
中定义的 app: fluentd
标签,然后为 DaemonSet 分配 fluentd
服务帐户。 我们还选择了 app: fluentd
作为这个 DaemonSet 管理的 Pod。
接下来,我们定义一个 NoSchedule
容差来匹配 Kubernetes 主节点上的等效污点。 这将确保 DaemonSet 也被推广到 Kubernetes 主服务器。 如果您不想在主节点上运行 Fluentd Pod,请删除此容忍度。 要了解有关 Kubernetes 污点和容忍度的更多信息,请参阅 Kubernetes 官方文档中的“污点和容忍度”。
接下来,我们开始定义 Pod 容器,我们称之为 fluentd
。
我们使用 Fluentd 维护者提供的 official v1.4.2 Debian image。 如果您想使用自己的私有或公共 Fluentd 镜像,或者使用不同的镜像版本,请修改容器规范中的 image
标签。 此镜像的 Dockerfile 和内容可在 Fluentd 的 fluentd-kubernetes-daemonset Github repo 中找到。
接下来,我们使用一些环境变量来配置 Fluentd:
FLUENT_ELASTICSEARCH_HOST
:我们将其设置为之前定义的 Elasticsearch 无头服务地址:elasticsearch.kube-logging.svc.cluster.local
。 这将解析为 3 个 Elasticsearch Pod 的 IP 地址列表。 实际的 Elasticsearch 主机很可能是此列表中返回的第一个 IP 地址。 要在集群中分发日志,您需要修改 Fluentd 的 Elasticsearch 输出插件的配置。 要了解有关此插件的更多信息,请参阅 Elasticsearch 输出插件 。FLUENT_ELASTICSEARCH_PORT
:我们将此设置为我们之前配置的 Elasticsearch 端口,9200
。FLUENT_ELASTICSEARCH_SCHEME
:我们将其设置为http
。FLUENTD_SYSTEMD_CONF
:我们将其设置为disable
以抑制与未在容器中设置的systemd
相关的输出。
最后,粘贴到以下部分:
流利的.yaml
. . . 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 Pod 上指定了 512 MiB 的内存限制,并保证 0.1vCPU 和 200MiB 的内存。 您可以根据预期的日志量和可用资源调整这些资源限制和请求。
接下来,我们使用 varlog
和 varlibdockercontainers
volumeMounts
将 /var/log
和 /var/lib/docker/containers
主机路径挂载到容器中。 这些 volumes
定义在块的末尾。
我们在此块中定义的最后一个参数是 terminationGracePeriodSeconds
,它使 Fluentd 在收到 SIGTERM
信号后有 30 秒的时间正常关闭。 30 秒后,容器会收到一个 SIGKILL
信号。 terminationGracePeriodSeconds
的默认值为30s,所以大多数情况下可以省略该参数。 要了解有关优雅终止 Kubernetes 工作负载的更多信息,请参阅 Google 的“Kubernetes 最佳实践:优雅终止 ”。
整个 Fluentd 规范应如下所示:
流利的.yaml
apiVersion: v1 kind: ServiceAccount metadata: name: fluentd namespace: kube-logging labels: app: fluentd --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: fluentd labels: app: fluentd rules: - apiGroups: - "" resources: - pods - namespaces verbs: - get - list - watch --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: fluentd roleRef: kind: ClusterRole name: fluentd apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount name: fluentd namespace: kube-logging --- apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd namespace: kube-logging labels: app: fluentd spec: selector: matchLabels: app: fluentd template: metadata: labels: app: fluentd spec: serviceAccount: fluentd serviceAccountName: fluentd tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: fluentd image: fluent/fluentd-kubernetes-daemonset:v1.4.2-debian-elasticsearch-1.1 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 DaemonSet 的配置后,保存并关闭文件。
现在,使用 kubectl
推出 DaemonSet:
kubectl create -f fluentd.yaml
您应该看到以下输出:
Outputserviceaccount/fluentd created clusterrole.rbac.authorization.k8s.io/fluentd created clusterrolebinding.rbac.authorization.k8s.io/fluentd created daemonset.extensions/fluentd created
使用 kubectl
验证您的 DaemonSet 是否成功推出:
kubectl get ds --namespace=kube-logging
您应该看到以下状态输出:
OutputNAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE fluentd 3 3 3 3 3 <none> 58s
这表示有 3 个 fluentd
Pod 在运行,这对应于我们 Kubernetes 集群中的节点数。
我们现在可以检查 Kibana 以验证日志数据是否被正确收集并传送到 Elasticsearch。
在 kubectl port-forward
仍然打开的情况下,导航到 http://localhost:5601
。
点击左侧导航菜单中的 Discover:
您应该看到以下配置窗口:
这允许您定义要在 Kibana 中探索的 Elasticsearch 索引。 要了解更多信息,请参阅 Kibana 官方文档中的 Defining your index patterns。 现在,我们将只使用 logstash-*
通配符模式来捕获 Elasticsearch 集群中的所有日志数据。 在文本框中输入logstash-*
,点击下一步。
然后您将被带到以下页面:
这允许您配置 Kibana 将使用哪个字段来按时间过滤日志数据。 在下拉列表中,选择 @timestamp 字段,然后点击 Create index pattern。
现在,点击左侧导航菜单中的 Discover。
您应该看到一个直方图和一些最近的日志条目:
至此,您已在 Kubernetes 集群上成功配置并推出了 EFK 堆栈。 要了解如何使用 Kibana 分析您的日志数据,请参阅 Kibana 用户指南。
在下一个可选部分中,我们将部署一个简单的计数器 Pod,它将数字打印到标准输出,并在 Kibana 中找到它的日志。
第 5 步(可选)- 测试容器日志记录
为了演示探索给定 Pod 的最新日志的基本 Kibana 用例,我们将部署一个最小的计数器 Pod,它将序列号打印到标准输出。
让我们从创建 Pod 开始。 在您喜欢的编辑器中打开一个名为 counter.yaml
的文件:
nano counter.yaml
然后,粘贴以下 Pod 规范:
计数器.yaml
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)"; i=$((i+1)); sleep 1; done']
保存并关闭文件。
这是一个名为 counter
的最小 Pod,它运行 while
循环,按顺序打印数字。
使用 kubectl
部署 counter
Pod:
kubectl create -f counter.yaml
Pod 创建并运行后,导航回 Kibana 仪表板。
在 Discover 页面中,在搜索栏中输入 kubernetes.pod_name:counter
。 这会过滤名为 counter
的 Pod 的日志数据。
然后,您应该会看到 counter
Pod 的日志条目列表:
您可以单击任何日志条目以查看其他元数据,例如容器名称、Kubernetes 节点、命名空间等。
结论
在本指南中,我们演示了如何在 Kubernetes 集群上设置和配置 Elasticsearch、Fluentd 和 Kibana。 我们使用了一个最小的日志架构,它由在每个 Kubernetes 工作节点上运行的单个日志代理 Pod 组成。
在将此日志堆栈部署到生产 Kubernetes 集群之前,最好按照本指南中的指示调整资源要求和限制。 您可能还需要设置 X-Pack 以启用内置监控和安全功能。
我们在这里使用的日志架构由 3 个 Elasticsearch Pod、一个 Kibana Pod(非负载均衡)和一组作为 DaemonSet 推出的 Fluentd Pod 组成。 您可能希望根据您的生产用例扩展此设置。 要了解有关扩展 Elasticsearch 和 Kibana 堆栈的更多信息,请参阅 Scaling Elasticsearch。
Kubernetes 还允许更复杂的日志代理架构,可能更适合您的用例。 要了解更多信息,请参阅 Kubernetes 文档中的 Logging Architecture。