百億架構之filebeat使用介紹

此係列文章一共分爲三部分,分爲filebeat部分,logstash部分,es部分。這裏會按照天天幾百億條的數據量來考慮,去設計、部署、優化這個日誌系統,來最大限度的利用資源,並達到一個最優的性能。本篇主要講解filebeat這一塊javascript

介紹

版本:filebeat-7.12.0java

這是關於k8s的日誌採集,部署方式是採用DaemonSet的方式,採集時按照k8s集羣的namespace進行分類,而後根據namespace的名稱建立不一樣的topic到kafka中node

image-20210419115501654

k8s日誌文件說明

通常狀況下,容器中的日誌在輸出到標準輸出(stdout)時,會以*-json.log的命名方式保存在/var/lib/docker/containers目錄中,固然若是修改了docker的數據目錄,那就是在修改後的數據目錄中了,例如:linux

# tree /data/docker/containers
/data/docker/containers
├── 009227c00e48b051b6f5cb65128fd58412b845e0c6d2bec5904f977ef0ec604d
│   ├── 009227c00e48b051b6f5cb65128fd58412b845e0c6d2bec5904f977ef0ec604d-json.log
│   ├── checkpoints
│   ├── config.v2.json
│   ├── hostconfig.json
│   └── mounts
複製代碼

這裏能看到,有這麼個文件: /data/docker/containers/container id/*-json.log,而後k8s默認會在/var/log/containers/var/log/pods目錄中會生成這些日誌文件的軟鏈接,以下所示:git

cattle-node-agent-tvhlq_cattle-system_agent-8accba2d42cbc907a412be9ea3a628a90624fb8ef0b9aa2bc6ff10eab21cf702.log
etcd-k8s-master01_kube-system_etcd-248e250c64d89ee6b03e4ca28ba364385a443cc220af2863014b923e7f982800.log
複製代碼

而後,會看到這個目錄下存在了此宿主機上的全部容器日誌,文件的命名方式爲:github

[podName]_[nameSpace]_[depoymentName]-[containerId].log
複製代碼

上面這個是deployment的命名方式,其餘的會有些不一樣,例如:DaemonSetStatefulSet等,不過全部的都有一個共同點,就是docker

*_[nameSpace]_*.log
複製代碼

到這裏,知道這個特性,就能夠往下來看Filebeat的部署和配置了。json

filebeat部署

部署採用的DaemonSet方式進行,這裏沒有啥可說的,參照官方文檔直接部署便可api

---
apiVersion: v1
data:
  filebeat.yml: |- filebeat.inputs: - type: container enabled: true paths: - /var/log/containers/*_test-1_*log fields: namespace: test-1 env: dev k8s: cluster-dev - type: container enabled: true paths: - /var/log/containers/*_test-2_*log fields: namespace: test-2 env: dev k8s: cluster-dev filebeat.config.modules: path: ${path.config}/modules.d/*.yml reload.enabled: false output.kafka: hosts: ["10.0.105.74:9092","10.0.105.76:9092","10.0.105.96:9092"] topic: '%{[fields.k8s]}-%{[fields.namespace]}' partition.round_robin: reachable_only: true kind: ConfigMap
metadata:
  name: filebeat-daemonset-config-test
  namespace: default
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: filebeat
  namespace: kube-system
  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: /var/lib/docker/containers
          readOnly: true
        - name: varlog
          mountPath: /var/log
          readOnly: true
      volumes:
      - name: config
        configMap:
          defaultMode: 0640
          name: filebeat-config
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: varlog
        hostPath:
          path: /var/log
      # 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: kube-system
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: kube-system
  labels:
    k8s-app: filebeat
複製代碼

啓動的話直接kubectl apply -f啓動便可,部署不是本篇的重點,這裏不作過多介紹。bash

官方部署參考:raw.githubusercontent.com/elastic/bea…

filebeat配置簡單介紹

這裏先簡單介紹下filebeat的配置結構

filebeat.inputs:

filebeat.config.modules:

processors:

output.xxxxx:

複製代碼

結構大概是這麼個結構,完整的數據流向簡單來講就是下面這個圖:

image-20210419225848048

前面也說了,我是根據根據命名空間作分類,每個命名空間就是一個topic,若是要收集多個集羣,一樣也是使用命名空間作分類,只不過topic的命名就須要加個k8s的集羣名,這樣方便去區分了,那既然是經過命名空間來獲取日誌,那麼在配置inputs的時候就須要經過寫正則將指定命名空間下的日誌文件取出,而後讀取,例如:

filebeat.inputs:
- type: container
  enabled: true
  paths:
  - /var/log/containers/*_test-1_*log
  fields:
    namespace: test-1
    env: dev
    k8s: cluster-dev
複製代碼

這裏個人命名空間爲bim5d-basic,而後經過正則*_test-1_*log來獲取帶有此命名空間名的日誌文件,隨後又加了個自定義字段,方便下面建立topic時使用。
這裏是寫了一個命名空間,若是有多個,就排開寫就好了,以下所示:

filebeat.inputs:
- type: container
  enabled: true
  paths:
  - /var/log/containers/*_test-1_*log
  fields:
    namespace: test-1
    env: dev
    k8s: cluster-dev
- type: container
  enabled: true
  paths:
  - /var/log/containers/*_test-2_*log
  fields:
    namespace: test-2
    env: dev
    k8s: cluster-dev
複製代碼

這種寫法有一個很差的地方就是,若是命名空間比較多,那麼整個配置就比較多,不要着急,文章下面有更簡潔的寫法

注意: 日誌的類型,要設置成container

上面說了經過命名空間建立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
複製代碼

注意這裏的寫法:%{[fields.namespace]}

那麼完整的配置以下所示:

apiVersion: v1
data:
  filebeat.yml: |- filebeat.inputs: - type: container enabled: true paths: - /var/log/containers/*_test-1_*log fields: namespace: test-1 env: dev k8s: cluster-dev - type: container enabled: true paths: - /var/log/containers/*_test-2_*log fields: namespace: test-2 env: dev k8s: cluster-dev filebeat.config.modules: path: ${path.config}/modules.d/*.yml reload.enabled: false output.kafka: hosts: ["10.0.105.74:9092","10.0.105.76:9092","10.0.105.96:9092"] topic: '%{[fields.k8s]}-%{[fields.namespace]}' partition.round_robin: reachable_only: true kind: ConfigMap
metadata:
  name: filebeat-daemonset-config-test
  namespace: default
複製代碼

若是是不對日誌作任何處理,到這裏就結束了,可是這樣又視乎在查看日誌的時候少了點什麼? 沒錯!到這裏你僅僅知道日誌內容,和該日誌來自於哪一個命名空間,可是你不知道該日誌屬於哪一個服務,哪一個pod,甚至說想查看該服務的鏡像地址等,可是這些信息在咱們上面的配置方式中是沒有的,因此須要進一步的添磚加瓦。

這個時候就用到了一個配置項,叫作: processors, 看下官方的解釋

You can define processors in your configuration to process events before they are sent to the configured output

簡單來講就是處理日誌

下面來重點講一下這個地方,很是有用和重要

filebeat的processors使用介紹

添加K8s的基本信息

在採集k8s的日誌時,若是按照上面那種配置方式,是沒有關於pod的一些信息的,例如:

  • Pod Name
  • Pod UID
  • Namespace
  • Labels
  • 等等等等

那麼若是想添加這些信息,就要使用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-04-19T07:07:36.065Z",
  "@metadata": {
    "beat": "filebeat",
    "type": "_doc",
    "version": "7.11.2"
  },
  "log": {
    "offset": 377708,
    "file": {
      "path": "/var/log/containers/test-server-85545c868b-6nsvc_test-1_test-server-885412c0a8af6bfa7b3d7a341c3a9cb79a85986965e363e87529b31cb650aec4.log"
    }
  },
  "fields": {
    "env": "dev",
    "k8s": "cluster-dev"
    "namespace": "test-1"
  },
  "host": {
    "name": "filebeat-fv484"
  },
  "agent": {
    "id": "7afbca43-3ec1-4cee-b5cb-1de1e955b717",
    "name": "filebeat-fv484",
    "type": "filebeat",
    "version": "7.11.2",
    "hostname": "filebeat-fv484",
    "ephemeral_id": "8fd29dee-da50-4c88-88d5-ebb6bbf20772"
  },
  "ecs": {
    "version": "1.6.0"
  },
  "stream": "stdout",
  "message": "2021-04-19 15:07:36.065 INFO 23 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration",
  "input": {
    "type": "container"
  },
  "container": {
    "image": {
      "name": "hub.test.com/test/test-server:3.3.1-ent-release-SNAPSHOT.20210402191241_87c9b1f841c"
    },
    "id": "885412c0a8af6bfa7b3d7a341c3a9cb79a85986965e363e87529b31cb650aec4",
    "runtime": "docker"
  },
  "kubernetes": {
    "labels": {
      "pod-template-hash": "85545c868b",
      "app": "geip-gateway-test"
    },
    "container": {
      "name": "test-server",
      "image": "hub.test.com/test/test-server:3.3.1-ent-release-SNAPSHOT.20210402191241_87c9b1f841c"
    },
    "node": {
      "uid": "511d9dc1-a84e-4948-b6c8-26d3f1ba2e61",
      "labels": {
        "kubernetes_io/hostname": "k8s-node-09",
        "kubernetes_io/os": "linux",
        "beta_kubernetes_io/arch": "amd64",
        "beta_kubernetes_io/os": "linux",
        "cloudt-global": "true",
        "kubernetes_io/arch": "amd64"
      },
      "hostname": "k8s-node-09",
      "name": "k8s-node-09"
    },
    "namespace_uid": "4fbea846-44b8-4d4a-b03b-56e43cff2754",
    "namespace_labels": {
      "field_cattle_io/projectId": "p-lgxhz",
      "cattle_io/creator": "norman"
    },
    "pod": {
      "name": "test-server-85545c868b-6nsvc",
      "uid": "1e678b63-fb3c-40b5-8aad-892596c5bd4d"
    },
    "namespace": "test-1",
    "replicaset": {
      "name": "test-server-85545c868b"
    }
  }
}
複製代碼

能夠看到kubernetes這個key的value有關於pod的信息,還有node的一些信息,還有namespace信息等,基本上關於k8s的一些關鍵信息都包含了,很是的多和全。

可是,問題又來了,這一條日誌信息有點太多了,有一半多不是咱們想要的信息,因此,咱們須要去掉一些對於咱們沒有用的字段

刪除沒必要要的字段

processors:
  - drop_fields:
      #刪除的多餘字段
      fields:
        - host
        - ecs
        - log
        - agent
        - input
        - stream
        - container
      ignore_missing: true
複製代碼

元信息:@metadata是不能刪除的

添加日誌時間

經過上面的日誌信息,能夠看到是沒有單獨的一個關於日誌時間的字段的,雖然裏面有一個@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的字段,在後面使用的時候,就可使用這個字段了。

從新拼接k8s源信息

實際上,到這個程度就已經完成了咱們的全部需求了,可是添加完k8s的信息以後,多了不少無用的字段,而咱們若是想去掉那些沒用的字段用drop_fields也能夠,例以下面這種寫法:

processors:
  - drop_fields:
      #刪除的多餘字段
      fields:
        - kubernetes.pod.uid
        - kubernetes.namespace_uid
        - kubernetes.namespace_labels
        - kubernetes.node.uid
        - kubernetes.node.labels
        - kubernetes.replicaset
        - kubernetes.labels
        - kubernetes.node.name
      ignore_missing: true
複製代碼

這樣寫也能夠把無用的字段去掉,可是結構層級沒有變化,嵌套了不少層,最終結果可能變成這個樣子

{
  "@timestamp": "2021-04-19T07:07:36.065Z",
  "@metadata": {
    "beat": "filebeat",
    "type": "_doc",
    "version": "7.11.2"
  },
  "fields": {
    "env": "dev",
    "k8s": "cluster-dev"
    "namespace": "test-1"
  },
  "message": "2021-04-19 15:07:36.065 INFO 23 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration",
  "kubernetes": {
    "container": {
      "name": "test-server",
      "image": "hub.test.com/test/test-server:3.3.1-ent-release-SNAPSHOT.20210402191241_87c9b1f841c"
    },
    "node": {
      "hostname": "k8s-node-09"
    },
    "pod": {
      "name": "test-server-85545c868b-6nsvc"
    },
    "namespace": "test-1"
  }
}
複製代碼

這樣在後面使用es建立template的時候,就會嵌套好多層,查詢起來也很不方便,既然這樣那咱們就優化下這個層級結構,繼續script這個插件

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); } 複製代碼

這裏單首創建了一個字段k8s,字段裏包含:podName, nameSpace, imageAddr, hostName等關鍵信息,最後再把kubernetes這個字段drop掉就能夠了。最終結果以下:

{
  "@timestamp": "2021-04-19T07:07:36.065Z",
  "@metadata": {
    "beat": "filebeat",
    "type": "_doc",
    "version": "7.11.2"
  },
  "fields": {
    "env": "dev",
    "k8s": "cluster-dev"
    "namespace": "test-1"
  },
  "time": "2021-04-19 15:07:36.065",
  "message": "2021-04-19 15:07:36.065 INFO 23 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration",
  "k8s": {
      "podName": "test-server-85545c868b-6nsvc",
      "nameSpace": "test-1",
      "imageAddr": "hub.test.com/test/test-server:3.3.1-ent-release-SNAPSHOT.20210402191241_87c9b1f841c",
      "hostName": "k8s-node-09"
  }
}
複製代碼

這樣看起來就很是清爽了。可是仍是有些繁瑣,由於若是後面要加新的命名空間,那麼每加一次就須要再改一次配置,這樣也是很是的繁瑣,那麼還有沒有更好的方式呢?答案屎有的。

最終優化

既然經過output.kafka來建立topic的時候,能夠經過指定字段,那麼利用這一點,咱們就能夠這樣設置:

output.kafka:
      hosts: ["10.0.105.74:9092","10.0.105.76:9092","10.0.105.96:9092"]
      topic: '%{[fields.k8s]}-%{[k8s.nameSpace]}'  # 經過往日誌裏注入k8s的元信息來獲取命名空間
      partition.round_robin:
        reachable_only: true
複製代碼

以前還在fields下面建立了一個k8s的字段,用來區分來自不通的k8s集羣,那麼這樣,咱們就能夠把這個配置文件優化成最終這個樣子

apiVersion: v1
data:
  filebeat.yml: |- filebeat.inputs: - type: container enabled: true paths: - /var/log/containers/*.log multiline.pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}|^[1-9]\d*\.[1-9]\d*\.[1-9]\d*\.[1-9]\d*' multiline.negate: true multiline.match: after multiline.timeout: 10s filebeat.config.modules: path: ${path.config}/modules.d/*.yml reload.enabled: false processors: - drop_event.when.regexp: or: kubernetes.pod.name: "filebeat.*" kubernetes.pod.name: "external-dns.*" kubernetes.pod.name: "coredns.*" kubernetes.pod.name: "eureka.*" kubernetes.pod.name: "zookeeper.*" - 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' # 下面這個腳本配置能夠忽略,本意是想經過獲取timestamp的時間轉化爲時間戳,而後再轉換爲本地時間 - script: lang: javascript id: format_time_utc tag: enable source: > function process(event) { var utc_time=event.Get("@timestamp"); var T_pos = utc_time.indexOf('T'); var Z_pos = utc_time.indexOf('Z'); var year_month_day = utc_time.substr(0, T_pos); var hour_minute_second = utc_time.substr(T_pos+1, Z_pos-T_pos-1); var new_time = year_month_day + " " + hour_minute_second; timestamp = new Date(Date.parse(new_time)); timestamp = timestamp.getTime(); timestamp = timestamp/1000; var timestamp = timestamp + 8*60*60; var bj_time = new Date(parseInt(timestamp) * 1000 + 8* 3600 * 1000); var bj_time = bj_time.toJSON().substr(0, 19).replace('T', ' '); event.Put("time_utc", bj_time); } - timestamp: field: time_utc layouts: - '2006-01-02 15:04:05' - '2006-01-02 15:04:05.999' test: - '2019-06-22 16:33:51' - add_fields: target: '' fields: env: prod - add_kubernetes_metadata: default_indexers.enabled: true default_matchers.enabled: true host: ${NODE_NAME} matchers: - logs_path: logs_path: "/var/log/containers/" - 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, k8sName: "sg-saas-pro-hbali" } event.Put("k8s", newK8s); } - drop_fields: #刪除的多餘字段 fields: - host - tags - ecs - log - prospector - agent - input - beat - offset - stream - container - kubernetes ignore_missing: true output.kafka: hosts: ["10.127.91.90:9092","10.127.91.91:9092","10.127.91.92:9092"] topic: '%{[k8s.k8sName]}-%{[k8s.nameSpace]}' partition.round_robin: reachable_only: true kind: ConfigMap
metadata:
  name: filebeat-daemonset-config
  namespace: default
複製代碼

作了一些調整,須要記住這裏建立topic時的方式%{[k8s.k8sName]}-%{[k8s.nameSpace]},後面使用logstash時還會用到。

總結

我的認爲讓filebeat在收集日誌的第一層作一些處理,能縮短整個過程的處理時間,由於瓶頸大多在es和logstash,因此一些耗時的操做盡可能在filebeat這塊去處理,若是處理不了在使用logstash,另一個很是容易忽視的一點就是,對於日誌內容的簡化,這樣能顯著下降日誌的體積,我作過測試,一樣的日誌條數,未作簡化的體積達到20G,而優化後的體積不到10G,這樣的話對於整個es集羣來講能夠說是很是的友好和做用巨大了。


歡迎各位朋友關注個人公衆號,來一塊兒學習進步哦

相關文章
相關標籤/搜索