> For the complete documentation index, see [llms.txt](https://training.onedoggo.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://training.onedoggo.com/tech-sharing/uncle-joe-teach-es-elastc-observability/logs-wa-jue-xi-tong-nei-bu-fa-sheng-de-zhuang-kuang/tou-guo-filebeat-shou-ji-infrastructure-zhong-ge-zhong-fu-wu-de-xi-jie-zi-xun.md).

# 透過 Filebeat 收集 Infrastructure 中各種服務的細節資訊

### 本篇學習重點

* 使用 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 指定 `access` 與 `error` Logs 的檔案路徑。                        |
| Java Backend Service | Application logs           | `Log` 或 `filestream` |          | 由於 Logs 的格式是自訂義的，直接使用 `filestream` 來指定要 Logs 的檔案來源路徑，甚至要特別處理的 `processors`。 |
| MySQL Database       | MySQL logs                 |                      | `MySQL`  | 透過 `MySQL` module 指定 `error` 與 `slowlog` Logs 的檔案路徑。                        |
| 主機本身                 | 主機本身的系統日誌，或是 auth logs     |                      | `System` | 透過 `System` module 指定 `syslog` 與 `auth` Logs 的檔案路徑。                         |

### 使用 Shared Drive 的方式收集 Logs

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

使用這種方式，在 Filebeat 的 **Inputs** 與 **Modules** 的配置上，沒有特別的差異，只是會一口氣將這些配置設定在同一個 Filebeat 身上，不過有個地方要特別注意，Filebeat 的 `Log` 與 `filestream` input，在使用網路共享或是雲端應商的儲存空間時，因為會使用磁碟機的 `inode` 資訊與 `device id` 當作檔案的唯一識別，有時 shared drive 的這個值會改變，導致於已經處理過的檔案，又重新被判斷成是新的檔案，造成重覆的傳送，這部份會要透過自行產生唯一的識別 ID，並且指定 `inode_maker` 來避免這件事發生，細節請參考 [官方文件 - Filebeat Input - Logs](https://www.elastic.co/guide/en/beats/filebeat/7.15/filebeat-input-log.html#file-identity)。

## 使用 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](https://www.elastic.co/guide/en/beats/filebeat/7.15/filebeat-input-container.html)。

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

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

如同我們前面 [使用 Metricbeat 掌握 Infrastructure 的健康狀態 Kubernetes 篇](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/shi-yong-metricbeat-zhang-wo-infrastructure-de-jian-kang-zhuang-tai-kubernetes-pian.md) 在介紹 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](https://raw.githubusercontent.com/elastic/beats/master/deploy/kubernetes/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_metadata` 的 **processor**，讓這些一口氣收集進來的各種 Pod 產生的 logs，加上 `Pod Name`、`Pod UID`、`Namespace`、`Labels` 的資訊，讓我們在後續分析處理時，能分辨得出來是哪邊產生的 log。
* 有一段是註解掉的 autodiscovery 的配置，若是使用 Docker、Kubernetes、或是使用 Nomad 時，可以考慮使用 autodiscovery 的機制，讓 container 或是 pod 動態增加時，也能夠由 DaemonSet 自動開始收集新增加容器產生的 logs。

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

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

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

## 參考資料

1. [官方文件 - Filebeat Input - Logs](https://www.elastic.co/guide/en/beats/filebeat/7.15/filebeat-input-log.html#file-identity)
2. [官方文件 - Filebeat Input - Container](https://www.elastic.co/guide/en/beats/filebeat/7.15/filebeat-input-container.html)
