iT邦幫忙

2021 iThome 鐵人賽

DAY 16
1
DevOps

喬叔帶你上手 Elastic Stack - 探索與實踐 Observability系列 第 16

16 - Logs - 挖掘系統內部發生的狀況 (4/4) - 透過 Filebeat 收集 Infrastructure 中各種服務的細節資訊

Logs - 挖掘系統內部發生的狀況 系列文章


本篇學習重點

  • 使用 Filebeat 收集使用主機 (Host) 所佈署的 Infrastructure 當中 Logs 的方式
  • 使用 Filebeat 收集使用容器 (Container) 佈署的 Infrastructure 當中 Logs 的建議做法

使用 Filebeat 收集使用主機 (Host) 所佈署的 Infrastructure 當中的 Logs

當我們使用非容器化的佈署方式,也就是直接在各主機上安裝服務或應用程式時,我們要收集這些機器上的 Logs 時,可能會有二種方式:

直接在服務的機器上安裝 Filebeat

例如我們在一個單純的三層式服務架構之中,有以下三種服務的主機:

  • Apache Web Server
  • Java Backend Service
  • MySQL Database

而我們要收集這幾台機器上的 Logs 時,我們會建議直接在每台主機上安裝 Filebeat,並且在不同的機器上配置不同的 Inputs 與 Modules 設定值,來收取我們希望取得的 Logs,例如:

主機所安裝的服務 希望收集的資訊 Inputs Modules 說明
Apache Web Server Apache access & error logs Apache 透過 Apache module 指定 accesserror Logs 的檔案路徑。
Java Backend Service Application logs Logfilestream 由於 Logs 的格式是自訂義的,直接使用 filestream 來指定要 Logs 的檔案來源路徑,甚至要特別處理的 processors
MySQL Database MySQL logs MySQL 透過 MySQL module 指定 errorslowlog Logs 的檔案路徑。
主機本身 主機本身的系統日誌,或是 auth logs System 透過 System module 指定 syslogauth Logs 的檔案路徑。

使用 Shared Drive 的方式收集 Logs

如果有使用 Shared drive 的方式,來簡化『集中化收集日誌』這件事的話,基本上就是在專門運行 Filebeat 的機器上,透過 NFS 之類的 Shared drives 取得各服務的日誌。

使用這種方式,在 Filebeat 的 InputsModules 的配置上,沒有特別的差異,只是會一口氣將這些配置設定在同一個 Filebeat 身上,不過有個地方要特別注意,Filebeat 的 Logfilestream input,在使用網路共享或是雲端應商的儲存空間時,因為會使用磁碟機的 inode 資訊與 device id 當作檔案的唯一識別,有時 shared drive 的這個值會改變,導致於已經處理過的檔案,又重新被判斷成是新的檔案,造成重覆的傳送,這部份會要透過自行產生唯一的識別 ID,並且指定 inode_maker 來避免這件事發生,細節請參考 官方文件 - Filebeat Input - Logs

使用 Filebeat 收集使用容器 (Container) 佈署的 Infrastructure 當中的 Logs

Docker 環境的 Log 產生方式

當我們使用 Docker 來佈署服務時,一般的做法都是在 Container 之中直接把 log 輸出到 stdout 或是 stderr,並透過 Docker logging driver 去進行處理,而實際的檔案預設會寫在 Docker host 身上,例如在 linux 環境中,會寫到 /var/lib/docker/containers/ 的路徑底下。

注意:因為容器的生命週期可能隨時會中斷,一個有良好 scalability 的容器化架構的設計,會是 stateless 的,也就是當容器被關閉時,存在裡面的 logs 就消失了,所以一般絕對不建議把 logs 直接寫在 container 之中,至少也要使用 volume 的方式將 logs 寫到會持久保存的另外的儲存空間裡。

收集所有 Docker Conainer 產生的 Logs

因此我們要使用 Filebeat 來收集所有 Docker Container 產生的 logs 時,我們其實就是針對 Docker host 的這個路徑裡的 logs 下手,這也是 Filebeat Container Inputs 所使用的方法。

注意:Docker Input 已經在 7.2 版時棄用 (deprecated) 了,之後請直接使用 Container Input。

而 Container Input 的設定方式如下:

filebeat.inputs:
- type: container
  paths: 
    - '/var/lib/docker/containers/*/*.log'

裡面也可以在 stream 屬性去設定只要針對 stdout 或是 stderr 的內容進行收集,細節可以參考 官方文件 - Filebeat Input - Container

注意:在這種使用一口氣收集所有 Containers 產生的 logs 時,應該要搭配一些內建的 Processors,例如 add_docker_metadataadd_kubernetes_metadataadd_cloud_metadata,將產生 logs 的 container 或是 pod 的資訊增加在 logs 之中,以便於後續使用時能進行分辨。

使用 Kubernetes DaemonSet 收集 K8S Nodes 身上各 Pods 的 Logs

如同我們前面 11 - Metrics - 觀察系統的健康指標 (5/6) - 使用 Metricbeat 掌握 Infrastructure 的健康狀態 Kubernetes 篇 在介紹 Metricbea 時,提到在 Kubernetes 使用 DaemonSet 的方式同樣的考量,可以確保每個 Node 身上有一個獨立的 Filebeat pod 來進行收集整個 Nodes 上所有 Pods 裡的 logs,並且傳送到 Elasticsearch。

而在 Kubernetes 裡,也如同前面『 Docker 環境的 Log 產生方式』運作的方式一樣,因為 Pods 會是 stateless,因此一般我們也都會直接將 logs 透過 stdout 或是 stderr 送出,並且由運行這些 Pods 所在的 Node,寫在實體的 disk 路徑 /var/log/containers/*.log 之中,因此我們也就可以使用 DaemonSet 的方式來佈署 Filebeat,並且將主機的 /var/log mount 到 Filebeat 身上,讓他可以直接使用 Container Inputs 的方式,取的這個所有 Pods 產生的 logs。

例如以官方的 filebeat-kubernetes.yaml 為例 (以下僅截取 ConfigMap 與 DaemonSet 的主要配置,完整版請參考原始檔案):

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-config
  namespace: kube-system
  labels:
    k8s-app: filebeat
data:
  filebeat.yml: |-
    filebeat.inputs:
    - type: container
      paths:
        - /var/log/containers/*.log
      processors:
        - add_kubernetes_metadata:
            host: ${NODE_NAME}
            matchers:
            - logs_path:
                logs_path: "/var/log/containers/"

    # To enable hints based autodiscover, remove `filebeat.inputs` configuration and uncomment this:
    #filebeat.autodiscover:
    #  providers:
    #    - type: kubernetes
    #      node: ${NODE_NAME}
    #      hints.enabled: true
    #      hints.default_config:
    #        type: container
    #        paths:
    #          - /var/log/containers/*${data.kubernetes.container.id}.log

    processors:
      - add_cloud_metadata:
      - add_host_metadata:

    cloud.id: ${ELASTIC_CLOUD_ID}
    cloud.auth: ${ELASTIC_CLOUD_AUTH}

    output.elasticsearch:
      hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
      username: ${ELASTICSEARCH_USERNAME}
      password: ${ELASTICSEARCH_PASSWORD}
---
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:8.0.0
        args: [
          "-c", "/etc/filebeat.yml",
          "-e",
        ]
        env:
        - name: ELASTICSEARCH_HOST
          value: elasticsearch
        - name: ELASTICSEARCH_PORT
          value: "9200"
        - name: ELASTICSEARCH_USERNAME
          value: elastic
        - name: ELASTICSEARCH_PASSWORD
          value: changeme
        - name: ELASTIC_CLOUD_ID
          value:
        - name: ELASTIC_CLOUD_AUTH
          value:
        - 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
---
  • DaemonSet 有宣告 mount /var/lib/docker/container 以及 /var/log,讓 Filebeat 可以取得 container 的 logs。

  • DaemonSet 有另外宣告 /var/lib/filebeat-data 當作 data 的 volumeMounts 來掛載到 container 內的 /var/share/filebeat/data 路徑,讓這個 DaemonSet 的 Filebeat 運作時的檔案,是寫到實體主機的 disk 之中,不會因為重啟而造成資料的遺失。

  • 另外有特別使用 add_kubernetes_metadataprocessor,讓這些一口氣收集進來的各種 Pod 產生的 logs,加上 Pod NamePod UIDNamespaceLabels 的資訊,讓我們在後續分析處理時,能分辨得出來是哪邊產生的 log。

  • 有一段是註解掉的 autodiscovery 的配置,若是使用 Docker、Kubernetes、或是使用 Nomad 時,可以考慮使用 autodiscovery 的機制,讓 container 或是 pod 動態增加時,也能夠由 DaemonSet 自動開始收集新增加容器產生的 logs。

使用 Kubernetes SideCar 的方式收集 K8S Pod 裡服務產生的實體 Logs

如果 Service container 寫的是實體的 Logs 檔案,另一種方式是我們可以使用 SideCar 的方法將 Logs 轉成 stdoutstderr 的方式往外傳送,以使用我們先前建議的容器化收集 logs 的方法。

首先將 Pod 中的 Service container 與 Sidecar container 使用 Shared Volume,讓 Sidecar container 能夠直接取得 Service container 產生 的 logs,並且 SideCar container 再負責將這個 logs tail 出來,輸出到 stdoutstderr,這樣後續就能同樣的使用 DaemonSet 或是專門收集 log 的 pod 的機制,進行後續的處理。

參考資料

  1. 官方文件 - Filebeat Input - Logs
  2. 官方文件 - Filebeat Input - Container

上一篇
15 - Logs - 挖掘系統內部發生的狀況 (3/4) - 透過 Filebeat 收集 Elastic Stack 中各種服務的細節資訊
下一篇
17 - Traces - 觀察應用程式的效能瓶頸 (1/6) - Elastic APM 基本介紹
系列文
喬叔帶你上手 Elastic Stack - 探索與實踐 Observability31

尚未有邦友留言

立即登入留言