1 背景

在Kubernetes还未兴起的时代,业务部署几乎所有应用都采用单机部署,当压力增大时,IDC架构只能横向拓展服务器集群,增加算力,云计算兴起后,可以动态的调整已有服务器的配置,来分担压力,或者可以通过弹性伸缩来实现根据业务量大小、集群负载、监控等情况,动态的横向调整后端服务器的数量。日志作为应用系统的一部分,通常在系统异常时,用来排查故障,寻找原因,传统的日志处理凡是通常结合grepLinux常见的文本命令工具进行分析。

为了支持更快的开发、迭代效率,近年来开始了容器化改造,并开始了拥抱 Kubernetes 生态、业务全量上云等工作。在这阶段,日志无论从规模、种类都呈现爆炸式的增长,对日志进行数字化、智能化分析的需求也越来越高,因此统一的日志平台应运而生。

2 Kubernetes日志系统收集难点

单纯日志系统的解决方案非常多,相对也比较成熟,这里就不再去赘述,我们此次只针对 Kubernetes 上的日志系统建设而论。Kubernetes 上的日志方案相比我们之前基于物理机、虚拟机场景的日志方案有很大不同,例如:

  • 日志的形式变得更加复杂,不仅有物理机/虚拟机上的日志,还有容器的标准输出、容器内的文件、容器事件、Kubernetes 事件等等信息需要采集;
  • 环境的动态性变强,在 Kubernetes 中,机器的宕机、下线、上线、Pod销毁、扩容/缩容等都是常态,这种情况下日志的存在是瞬时的(例如如果 Pod 销毁后该 Pod 日志就不可见了),所以日志数据必须实时采集到服务端。同时还需要保证日志的采集能够适应这种动态性极强的场景;
  • 日志的种类变多,上图是一个典型的 Kubernetes 架构,一个请求从客户端需要经过 CDN、Ingress、Service Mesh、Pod 等多个组件,涉及多种基础设施,其中的日志种类增加了很多,例如 K8s 各种系统组件日志、审计日志、ServiceMesh 日志、Ingress 等;

3 Kubernetes日志文件说明

关于Kubenetes的日志采集,部署方式是采用DaemonSet的方式,采集时按照k8s集群的namespace进行分类,然后根据namespace的名称创建不同的topic到kafka中

一般情况下,Kubernetes默认会在/var/log/containers/var/log/pods目录中会生成这些日志文件的软连接,如下所示:

然后,会看到这个目录下存在了此宿主机上的所有容器日志,文件的命名方式为:

[podName]_[nameSpace]_[depoymentName]-[containerId].log 复制代码

上面这个是deployment的命名方式,其他的会有些不同,例如:DaemonSetStatefulSet等,不过所有的都有一个共同点,就是

*_[nameSpace]_*.log
复制代码

到这里,知道这个特性,就可以往下来看Filebeat的部署和配置了。

4 Filebeat

4.1 部署

部署采用的DaemonSet方式进行

---
apiVersion: v1
kind: ConfigMap metadata: name: filebeat-config namespace: log data: filebeat.yml: |- filebeat.inputs: - type: container enabled: true paths: - /var/log/containers/*_default_*.log fields: namespace: default env: dev k8s: cluster-dev - type: container enabled: true paths: - /var/log/containers/*_kube-system_*.log fields: namespace: kube-system env: dev k8s: cluster-dev filebeat.config.modules: path: ${path.config}/modules.d/*.yml reload.enabled: false output.kafka: hosts: ["175.27.159.78:9092","175.27.159.78:9093","175.27.159.78:9094"] topic: '%{[fields.k8s]}-%{[fields.namespace]}' partition.round_robin: reachable_only: true --- apiVersion: apps/v1 kind: DaemonSet metadata: name: filebeat namespace: log labels: k8s-app: filebeat spec: selector: matchLabels: k8s-app: filebeat template: metadata: labels: k8s-app: filebeat spec: serviceAccountName: filebeat terminationGracePeriodSeconds: 30 hostNetwork: true dnsPolicy: ClusterFirstWithHostNet containers: - name: filebeat image: docker.elastic.co/beats/filebeat:7.12.0 args: [ "-c", "/etc/filebeat.yml", "-e", ] env: - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName securityContext: runAsUser: 0 # If using Red Hat OpenShift uncomment this: #privileged: true resources: limits: memory: 200Mi requests: cpu: 100m memory: 100Mi volumeMounts: - name: config mountPath: /etc/filebeat.yml readOnly: true subPath: filebeat.yml - name: data mountPath: /usr/share/filebeat/data - name: varlibdockercontainers mountPath: /data/docker/containers readOnly: true - name: varlog mountPath: /var/log readOnly: true volumes: - name: config configMap: defaultMode: 0640 name: filebeat-config - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /data/docker/containers # data folder stores a registry of read status for all files, so we don't send everything again on a Filebeat pod restart - name: data hostPath: # When filebeat runs as non-root user, this directory needs to be writable by group (g+w). path: /var/lib/filebeat-data type: DirectoryOrCreate --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: filebeat subjects: - kind: ServiceAccount name: filebeat namespace: log roleRef: kind: ClusterRole name: filebeat apiGroup: rbac.authorization.k8s.io --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: filebeat labels: k8s-app: filebeat rules: - apiGroups: [""] # "" indicates the core API group resources: - namespaces - pods - nodes verbs: - get - watch - list - apiGroups: ["apps"] resources: - replicasets verbs: ["get", "list", "watch"] --- apiVersion: v1 kind: ServiceAccount metadata: name: filebeat namespace: log labels: k8s-app: filebeat 复制代码
[root@master filebeat]# kubectl apply -f filebeat-daemonset.yaml 
configmap/filebeat-daemonset-config-test created
daemonset.apps/filebeat created
clusterrolebinding.rbac.authorization.k8s.io/filebeat created
clusterrole.rbac.authorization.k8s.io/filebeat created
serviceaccount/filebeat created
复制代码

4.2 Filebeat配置文件介绍

这里先简单介绍下filebeat的配置结构

filebeat.inputs:

filebeat.config.modules:

processors:

output.xxxxx:
复制代码

结构大概是这么个结构,完整的数据流向简单来说就是下面这个图:

4.3 inputs

根据命名空间做分类,每一个命名空间就是一个topic,如果要收集多个集群,同样也是使用命名空间做分类,只不过topic的命名就需要加个k8s的集群名,这样方便去区分了,那既然是通过命名空间来获取日志,那么在配置inputs的时候就需要通过写正则将指定命名空间下的日志文件取出,然后读取,例如:

filebeat.inputs:
- type: container
  enabled: true paths: - /var/log/containers/*_default_*.log fields: namespace: default env: dev k8s: cluster-dev 复制代码

这里是写了一个命名空间,如果有多个,就排开写就行了,如下所示:

filebeat.inputs:
- type: container
  enabled: true paths: - /var/log/containers/*_default_*.log fields: namespace: default env: dev k8s: cluster-dev - type: container enabled: true paths: - /var/log/containers/*_kube-system_*.log fields: namespace: kube-system env: dev k8s: cluster-dev 复制代码

上面说了通过命名空间创建topic,我这里加了一个自定义的字段namespace,就是后面的topic的名称,但是这里有很多的命名空间,那在输出的时候,如何动态去创建呢?

output.kafka:
  hosts: ["10.0.105.74:9092","10.0.105.76:9092","10.0.105.96:9092"] topic: '%{[fields.namespace]}' partition.round_robin: reachable_only: true 复制代码

那么目前,完整的配置文件如下:

apiVersion: v1
kind: ConfigMap
metadata: name: filebeat-config namespace: log data: filebeat.yml: |- filebeat.inputs: - type: container enabled: true paths: - /var/log/containers/*_default_*.log fields: namespace: default env: dev k8s: cluster-dev - type: container enabled: true paths: - /var/log/containers/*_kube-system_*.log fields: namespace: kube-system env: dev k8s: cluster-dev filebeat.config.modules: path: ${path.config}/modules.d/*.yml reload.enabled: false output.kafka: hosts: ["175.27.159.78:9092","175.27.159.78:9093","175.27.159.78:9094"] topic: '%{[fields.k8s]}-%{[fields.namespace]}' partition.round_robin: reachable_only: true 复制代码

4.4 processors

如果是不对日志做任何处理,到这里就结束了,但是这样又视乎在查看日志的时候少了点什么? 到这里仅仅知道日志内容,和该日志来自于哪个命名空间,但是你=不知道该日志属于哪个服务,哪个pod,甚至说想查看该服务的镜像地址等,但是这些信息在我们上面的配置方式中是没有的,所以需要进一步的添砖加瓦。

这个时候就用到了一个配置项,叫做: processors

4.4.1 添加K8s的基本信息

在采集k8s的日志时,如果按照上面那种配置方式,是没有关于pod的一些信息的,例如:

  • Pod Name
  • Pod UID
  • Namespace
  • Labels

添加前日志示例

{
  "@timestamp": "2021-05-06T02:47:09.256Z", "@metadata": { "beat": "filebeat", "type": "_doc", "version": "7.12.0" }, "log": { "file": { "path": "/var/log/containers/metrics-server-5549c7694f-7vb66_kube-system_metrics-server-9108765e17c7e325abd665fb0f53c8f4b3077c698cb88392099dfbafb0475709.log" }, "offset": 15842 }, "stream": "stderr", "message": "E0506 02:47:09.254911 1 reststorage.go:160] unable to fetch pod metrics for pod log/filebeat-s67ds: no metrics known for pod", "input": { "type": "container" }, "fields": { "env": "dev", "k8s": "cluster-dev", "namespace": "kube-system" }, "ecs": { "version": "1.8.0" }, "host": { "name": "node-03" }, "agent": { "hostname": "node-03", "ephemeral_id": "1c87559a-cfca-4708-8f28-e4fc6441943c", "id": "f9cf0cd4-eccf-4d8b-bd24-2bff25b4083b", "name": "node-03", "type": "filebeat", "version": "7.12.0" } } 复制代码

那么如果想添加这些信息,就要使用processors中的一个工具,叫做: add_kubernetes_metadata, 字面意思就是添加k8s的一些元数据信息,使用方法可以先来看一段示例:

processors:
  - add_kubernetes_metadata:
      host: ${NODE_NAME} matchers: - logs_path: logs_path: "/var/log/containers/" 复制代码

host: 指定要对filebeat起作用的节点,防止无法准确检测到它,比如在主机网络模式下运行filebeat

matchers: 匹配器用于构造与索引创建的标识符相匹配的查找键

logs_path: 容器日志的基本路径,如果未指定,则使用Filebeat运行的平台的默认日志路径

加上这个k8s的元数据信息之后,就可以在日志里面看到k8s的信息了,看一下添加k8s信息后的日志格式

{
  "@timestamp": "2021-05-06T03:01:58.512Z", "@metadata": { "beat": "filebeat", "type": "_doc", "version": "7.12.0" }, "agent": { "hostname": "node-03", "ephemeral_id": "c0f94fc0-b128-4eb9-b9a3-387f4cae44b7", "id": "f9cf0cd4-eccf-4d8b-bd24-2bff25b4083b", "name": "node-03", "type": "filebeat", "version": "7.12.0" }, "ecs": { "version": "1.8.0" }, "stream": "stdout", "input": { "type": "container" }, "host": { "name": "node-03" }, "container": { "id": "6791d22d210507becd7306ead1eeda9a4c558b5ca0630ed5af4f8b1b220fb4a7", "runtime": "docker", "image": { "name": "nginx:1.10" } }, "kubernetes": { "namespace": "default", "replicaset": { "name": "nginx-5b946576d4" }, "labels": { "app": "nginx", "pod-template-hash": "5b946576d4" }, "container": { "name": "nginx", "image": "nginx:1.10" }, "deployment": { "name": "nginx" }, "node": { "name": "node-03", "uid": "4340750b-1bb4-4d61-a9aa-4715c7326988", "labels": { "kubernetes_io/arch": "amd64", "kubernetes_io/hostname": "node-03", "kubernetes_io/os": "linux", "beta_kubernetes_io/arch": "amd64", "beta_kubernetes_io/os": "linux" }, "hostname": "node-03" }, "namespace_uid": "8d1dad4b-bea0-469d-9858-51147822de79", "pod": { "name": "nginx-5b946576d4-6kftk", "uid": "cc8c943a-919c-4e15-9cde-05358b8588c1" } }, "log": { "offset": 2039, "file": { "path": "/var/log/containers/nginx-5b946576d4-6kftk_default_nginx-6791d22d210507becd7306ead1eeda9a4c558b5ca0630ed5af4f8b1b220fb4a7.log" } }, "message": "2021-05-06 11:01:58 10.234.2.11 - - \"GET / HTTP/1.1\" 200 612 \"-\" \"curl/7.29.0\" \"-\"", "fields": { "k8s": "cluster-dev", "namespace": "default", "env": "dev" } } 复制代码

可以看到kubernetes这个key的value有关于pod的信息,还有node的一些信息,还有namespace信息等,基本上关于k8s的一些关键信息都包含了,非常的多和全。

但是,问题又来了,这一条日志信息有点太多了,有一半多不是我们想要的信息,所以,我们需要去掉一些对于我们没有用的字段

4.4.2 删除不必要的字段

processors:
  - drop_fields:
      #删除的多余字段
      fields: - host - ecs - log - agent - input - stream - container ignore_missing: true 复制代码

4.4.3 添加日志时间

通过上面的日志信息,可以看到是没有单独的一个关于日志时间的字段的,虽然里面有一个@timestamp,但不是北京时间,而我们要的是日志的时间,message里面倒是有时间,但是怎么能把它取到并单独添加一个字段呢,这个时候就需要用到script了,需要写一个js脚本来替换。

processors:
  - script:
      lang: javascript id: format_time tag: enable source: > function process(event) { var str=event.Get("message"); var time=str.split(" ").slice(0, 2).join(" "); event.Put("time", time); } - timestamp: field: time timezone: Asia/Shanghai layouts: - '2006-01-02 15:04:05' - '2006-01-02 15:04:05.999' test: - '2019-06-22 16:33:51' 复制代码

添加完成后,会多一个time的字段,在后面使用的时候,就可以使用这个字段了。

{
  "@timestamp": "2021-05-06T04:32:10.560Z", "@metadata": { "beat": "filebeat", "type": "_doc", "version": "7.12.0" }, "message": "2021-05-06 11:32:10 10.234.2.11 - - \"GET / HTTP/1.1\" 200 612 \"-\" \"curl/7.29.0\" \"-\"", "fields": { "k8s": "cluster-dev", "namespace": "default", "env": "dev" }, "time": "2021-05-06 11:32:10", "kubernetes": { "replicaset": { "name": "nginx-deployment-6c4b886b" }, "labels": { "app": "nginx-deployment", "pod-template-hash": "6c4b886b" }, "container": { "name": "nginx", "image": "nginx:1.19.5" }, "deployment": { "name": "nginx-deployment" }, "node": { "uid": "07d8a1a4-e10f-4331-adf0-2fd7d5817c2d", "labels": { "beta_kubernetes_io/os": "linux", "kubernetes_io/arch": "amd64", "kubernetes_io/hostname": "node-02", "kubernetes_io/os": "linux", "beta_kubernetes_io/arch": "amd64" }, "hostname": "node-02", "name": "node-02" }, "namespace_uid": "8d1dad4b-bea0-469d-9858-51147822de79", "pod": { "name": "nginx-deployment-6c4b886b-6rbhw", "uid": "78a28548-3d34-4df6-9a76-c651b39ff934" }, "namespace": "default" } } 复制代码

4.4.4 优化k8s数据信息结构

这里单独创建了一个字段k8s,字段里包含:podName, nameSpace, imageAddr, hostName等关键信息,最后再把kubernetes这个字段drop掉就可以了。最终结果如下:

processors:
  - script:
      lang: javascript id: format_k8s tag: enable source: > function process(event) { var k8s=event.Get("kubernetes"); var newK8s = { podName: k8s.pod.name, nameSpace: k8s.namespace, imageAddr: k8s.container.name, hostName: k8s.node.hostname } event.Put("k8s", newK8s); } 复制代码

日志

{
  "@timestamp": "2021-05-06T05:33:25.351Z", "@metadata": { "beat": "filebeat", "type": "_doc", "version": "7.12.0" }, "fields": { "k8s": "cluster-dev", "namespace": "default", "env": "dev" }, "k8s": { "hostName": "node-02", "podName": "nginx-deployment-6c4b886b-6rbhw", "nameSpace": "default", "imageAddr": "nginx" }, "message": "06/May/2021:05:33:25 +0000 10.234.2.11 - - \"GET / HTTP/1.1\" 200 612 \"-\" \"curl/7.29.0\" \"-\"" } 复制代码

作者:Honest1y

链接:https://juejin.cn/post/6974581299881181220

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

原文地址:http://www.cnblogs.com/gaoyanbing/p/16897290.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性