k8s-搭建 EFK 日志系統


搭建 EFK 日志系統

大家介紹了 Kubernetes 集群中的幾種日志收集方案,Kubernetes 中比較流行的日志收集解決方案是 Elasticsearch、Fluentd 和 Kibana(EFK)技術棧,也是官方現在比較推薦的一種方案。

Elasticsearch 是一個實時的、分布式的可擴展的搜索引擎,允許進行全文、結構化搜索,它通常用於索引和搜索大量日志數據,也可用於搜索許多不同類型的文檔。

Elasticsearch 通常與 Kibana 一起部署,Kibana 是 Elasticsearch 的一個功能強大的數據可視化 Dashboard,Kibana 允許你通過 web 界面來瀏覽 Elasticsearch 日志數據。

Fluentd是一個流行的開源數據收集器,我們將在 Kubernetes 集群節點上安裝 Fluentd,通過獲取容器日志文件、過濾和轉換日志數據,然后將數據傳遞到 Elasticsearch 集群,在該集群中對其進行索引和存儲。

我們先來配置啟動一個可擴展的 Elasticsearch 集群,然后在 Kubernetes 集群中創建一個 Kibana 應用,最后通過 DaemonSet 來運行 Fluentd,以便它在每個 Kubernetes 工作節點上都可以運行一個 Pod。

創建 Elasticsearch 集群

在創建 Elasticsearch 集群之前,我們先創建一個命名空間,我們將在其中安裝所有日志相關的資源對象。

新建一個 kube-logging.yaml 文件:

apiVersion: v1 kind: Namespace metadata: name: logging 

然后通過 kubectl 創建該資源清單,創建一個名為 logging 的 namespace:

$ kubectl create -f kube-logging.yaml
namespace/logging created
$ kubectl get ns
NAME           STATUS    AGE
default        Active    244d
istio-system   Active    100d
kube-ops       Active    179d
kube-public    Active    244d
kube-system    Active    244d
logging        Active    4h
monitoring     Active    35d

現在創建了一個命名空間來存放我們的日志相關資源,接下來可以部署 EFK 相關組件,首先開始部署一個3節點的 Elasticsearch 集群。

這里我們使用3個 Elasticsearch Pod 來避免高可用下多節點集群中出現的“腦裂”問題,當一個或多個節點無法與其他節點通信時會產生“腦裂”,可能會出現幾個主節點。

了解更多 Elasticsearch 集群腦裂問題,可以查看文檔https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html#split-brain

一個關鍵點是您應該設置參數discover.zen.minimum_master_nodes=N/2+1,其中N是 Elasticsearch 集群中符合主節點的節點數,比如我們這里3個節點,意味着N應該設置為2。這樣,如果一個節點暫時與集群斷開連接,則另外兩個節點可以選擇一個新的主節點,並且集群可以在最后一個節點嘗試重新加入時繼續運行,在擴展 Elasticsearch 集群時,一定要記住這個參數。

首先創建一個名為 elasticsearch 的無頭服務,新建文件 elasticsearch-svc.yaml,文件內容如下:

kind: Service apiVersion: v1 metadata: name: elasticsearch namespace: logging labels: app: elasticsearch spec: selector: app: elasticsearch clusterIP: None ports: - port: 9200 name: rest - port: 9300 name: inter-node 

定義了一個名為 elasticsearch 的 Service,指定標簽app=elasticsearch,當我們將 Elasticsearch StatefulSet 與此服務關聯時,服務將返回帶有標簽app=elasticsearch的 Elasticsearch Pods 的 DNS A 記錄,然后設置clusterIP=None,將該服務設置成無頭服務。最后,我們分別定義端口9200、9300,分別用於與 REST API 交互,以及用於節點間通信。

使用 kubectl 直接創建上面的服務資源對象:

$ kubectl create -f elasticsearch-svc.yaml
service/elasticsearch created
$ kubectl get services --namespace=logging Output NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE elasticsearch ClusterIP None <none> 9200/TCP,9300/TCP 26s 

現在我們已經為 Pod 設置了無頭服務和一個穩定的域名.elasticsearch.logging.svc.cluster.local,接下來我們通過 StatefulSet 來創建具體的 Elasticsearch 的 Pod 應用。

Kubernetes StatefulSet 允許我們為 Pod 分配一個穩定的標識和持久化存儲,Elasticsearch 需要穩定的存儲來保證 Pod 在重新調度或者重啟后的數據依然不變,所以需要使用 StatefulSet 來管理 Pod。

要了解更多關於 StaefulSet 的信息,可以查看官網關於 StatefulSet 的相關文檔:https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/

新建名為 elasticsearch-statefulset.yaml 的資源清單文件,首先粘貼下面內容:

apiVersion: apps/v1 kind: StatefulSet metadata: name: es-cluster namespace: logging spec: serviceName: elasticsearch replicas: 3 selector: matchLabels: app: elasticsearch template: metadata: labels: app: elasticsearch 

該內容中,我們定義了一個名為 es-cluster 的 StatefulSet 對象,然后定義serviceName=elasticsearch和前面創建的 Service 相關聯,這可以確保使用以下 DNS 地址訪問 StatefulSet 中的每一個 Pod:es-cluster-[0,1,2].elasticsearch.logging.svc.cluster.local,其中[0,1,2]對應於已分配的 Pod 序號。

然后指定3個副本,將 matchLabels 設置為app=elasticsearch,所以 Pod 的模板部分.spec.template.metadata.lables也必須包含app=elasticsearch標簽。

然后定義 Pod 模板部分內容:

... spec: containers: - name: elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.3 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.zen.ping.unicast.hosts value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch" - name: discovery.zen.minimum_master_nodes value: "2" - name: ES_JAVA_OPTS value: "-Xms512m -Xmx512m" 

該部分是定義 StatefulSet 中的 Pod,我們這里使用一個-oss后綴的鏡像,該鏡像是 Elasticsearch 的開源版本,如果你想使用包含X-Pack之類的版本,可以去掉該后綴。然后暴露了9200和9300兩個端口,注意名稱要和上面定義的 Service 保持一致。然后通過 volumeMount 聲明了數據持久化目錄,下面我們再來定義 VolumeClaims。最后就是我們在容器中設置的一些環境變量了:

  • cluster.name:Elasticsearch 集群的名稱,我們這里命名成 k8s-logs。
  • node.name:節點的名稱,通過metadata.name來獲取。這將解析為 es-cluster-[0,1,2],取決於節點的指定順序。
  • discovery.zen.ping.unicast.hosts:此字段用於設置在 Elasticsearch 集群中節點相互連接的發現方法。我們使用 unicastdiscovery 方式,它為我們的集群指定了一個靜態主機列表。由於我們之前配置的無頭服務,我們的 Pod 具有唯一的 DNS 域es-cluster-[0,1,2].elasticsearch.logging.svc.cluster.local,因此我們相應地設置此變量。由於都在同一個 namespace 下面,所以我們可以將其縮短為es-cluster-[0,1,2].elasticsearch。要了解有關 Elasticsearch 發現的更多信息,請參閱 Elasticsearch 官方文檔:https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-discovery.html
  • discovery.zen.minimum_master_nodes:我們將其設置為(N/2) + 1N是我們的群集中符合主節點的節點的數量。我們有3個 Elasticsearch 節點,因此我們將此值設置為2(向下舍入到最接近的整數)。要了解有關此參數的更多信息,請參閱官方 Elasticsearch 文檔:https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html#split-brain
  • ES_JAVA_OPTS:這里我們設置為-Xms512m -Xmx512m,告訴JVM使用512 MB的最小和最大堆。您應該根據群集的資源可用性和需求調整這些參數。要了解更多信息,請參閱設置堆大小的相關文檔:https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html

接下來添加關於 initContainer 的內容:

... 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 

這里我們定義了幾個在主應用程序之前運行的 Init 容器,這些初始容器按照定義的順序依次執行,執行完成后才會啟動主應用容器。

第一個名為 fix-permissions 的容器用來運行 chown 命令,將 Elasticsearch 數據目錄的用戶和組更改為1000:1000(Elasticsearch 用戶的 UID)。因為默認情況下,Kubernetes 用 root 用戶掛載數據目錄,這會使得 Elasticsearch 無法方法該數據目錄,可以參考 Elasticsearch 生產中的一些默認注意事項相關文檔說明:https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#_notes_for_production_use_and_defaults

第二個名為 increase-vm-max-map 的容器用來增加操作系統對mmap計數的限制,默認情況下該值可能太低,導致內存不足的錯誤,要了解更多關於該設置的信息,可以查看 Elasticsearch 官方文檔說明:https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html

最后一個初始化容器是用來執行ulimit命令增加打開文件描述符的最大數量的。

此外 Elastisearch Notes for Production Use 文檔還提到了由於性能原因最好禁用 swap,當然對於 Kubernetes 集群而言,最好也是禁用 swap 分區的。

現在我們已經定義了主應用容器和它之前運行的 Init Containers 來調整一些必要的系統參數,接下來我們可以添加數據目錄的持久化相關的配置,在 StatefulSet 中,使用 volumeClaimTemplates 來定義 volume 模板即可:

... volumeClaimTemplates: - metadata: name: data labels: app: elasticsearch spec: accessModes: [ "ReadWriteOnce" ] storageClassName: es-data-db resources: requests: storage: 50Gi 

我們這里使用 volumeClaimTemplates 來定義持久化模板,Kubernetes 會使用它為 Pod 創建 PersistentVolume,設置訪問模式為ReadWriteOnce,這意味着它只能被 mount 到單個節點上進行讀寫,然后最重要的是使用了一個名為 es-data-db 的 StorageClass 對象,所以我們需要提前創建該對象,我們這里使用的 NFS 作為存儲后端,所以需要安裝一個對應的 provisioner 驅動,前面關於 StorageClass 的課程中已經和大家介紹過方法,新建一個 elasticsearch-storageclass.yaml 的文件,文件內容如下:

apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: es-data-db provisioner: fuseim.pri/ifs # 該值需要和 provisioner 配置的保持一致 

最后,我們指定了每個 PersistentVolume 的大小為 50GB,我們可以根據自己的實際需要進行調整該值。最后,完整的 Elasticsearch StatefulSet 資源清單文件內容如下:

apiVersion: apps/v1 kind: StatefulSet metadata: name: es-cluster namespace: 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-oss:6.4.3 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.zen.ping.unicast.hosts value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch" - name: discovery.zen.minimum_master_nodes value: "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: es-data-db resources: requests: storage: 100Gi 

現在直接使用 kubectl 工具部署即可:

$ kubectl create -f elasticsearch-storageclass.yaml
storageclass.storage.k8s.io "es-data-db" created $ kubectl create -f elasticsearch-statefulset.yaml statefulset.apps/es-cluster created 

添加成功后,可以看到 logging 命名空間下面的所有的資源對象:

$ kubectl get sts -n logging
NAME         DESIRED   CURRENT   AGE
es-cluster   3         3         20h
$ kubectl get pods -n logging
NAME                      READY     STATUS    RESTARTS   AGE
es-cluster-0              1/1       Running   0          20h
es-cluster-1              1/1       Running   0          20h
es-cluster-2              1/1       Running   0          20h
$ kubectl get svc -n logging
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S) AGE elasticsearch ClusterIP None <none> 9200/TCP,9300/TCP 20h 

Pods 部署完成后,我們可以通過請求一個 REST API 來檢查 Elasticsearch 集群是否正常運行。使用下面的命令將本地端口9200轉發到 Elasticsearch 節點(如es-cluster-0)對應的端口:

$ kubectl port-forward es-cluster-0 9200:9200 --namespace=logging Forwarding from 127.0.0.1:9200 -> 9200 Forwarding from [::1]:9200 -> 9200 

然后,在另外的終端窗口中,執行如下請求:

$ curl http://localhost:9200/_cluster/state?pretty 

正常來說,應該會看到類似於如下的信息:

{ "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.4.191:9300", "attributes" : { } }, "IdM5B7cUQWqFgIHXBp0JDg" : { "name" : "es-cluster-0", "ephemeral_id" : "JTk1FDdFQuWbSFAtBxdxAQ", "transport_address" : "10.244.2.215:9300", "attributes" : { } }, "R8E7xcSUSbGbgrhAdyAKmQ" : { "name" : "es-cluster-2", "ephemeral_id" : "9wv6ke71Qqy9vk2LgJTqaA", "transport_address" : "10.244.40.4:9300", "attributes" : { } } }, ... 

看到上面的信息就表明我們名為 k8s-logs 的 Elasticsearch 集群成功創建了3個節點:es-cluster-0,es-cluster-1,和es-cluster-2,當前主節點是 es-cluster-0。

創建 Kibana 服務

Elasticsearch 集群啟動成功了,接下來我們可以來部署 Kibana 服務,新建一個名為 kibana.yaml 的文件,對應的文件內容如下:

apiVersion: v1 kind: Service metadata: name: kibana namespace: logging labels: app: kibana spec: ports: - port: 5601 type: NodePort selector: app: kibana --- apiVersion: apps/v1 kind: Deployment metadata: name: kibana namespace: logging labels: app: kibana spec: selector: matchLabels: app: kibana template: metadata: labels: app: kibana spec: containers: - name: kibana image: docker.elastic.co/kibana/kibana-oss:6.4.3 resources: limits: cpu: 1000m requests: cpu: 100m env: - name: ELASTICSEARCH_URL value: http://elasticsearch:9200 ports: - containerPort: 5601 

上面我們定義了兩個資源對象,一個 Service 和 Deployment,為了測試方便,我們將 Service 設置為了 NodePort 類型,Kibana Pod 中配置都比較簡單,唯一需要注意的是我們使用 ELASTICSEARCH_URL 這個環境變量來設置Elasticsearch 集群的端點和端口,直接使用 Kubernetes DNS 即可,此端點對應服務名稱為 elasticsearch,由於是一個 headless service,所以該域將解析為3個 Elasticsearch Pod 的 IP 地址列表。

配置完成后,直接使用 kubectl 工具創建:

$ kubectl create -f kibana.yaml
service/kibana created
deployment.apps/kibana created

創建完成后,可以查看 Kibana Pod 的運行狀態:

$ kubectl get pods --namespace=logging NAME READY STATUS RESTARTS AGE es-cluster-0 1/1 Running 0 20h es-cluster-1 1/1 Running 0 20h es-cluster-2 1/1 Running 0 20h kibana-7558d4dc4d-5mqdz 1/1 Running 0 20h $ kubectl get svc --namespace=logging NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE elasticsearch ClusterIP None <none> 9200/TCP,9300/TCP 20h kibana NodePort 10.105.208.253 <none> 5601:31816/TCP 20h 

如果 Pod 已經是 Running 狀態了,證明應用已經部署成功了,然后可以通過 NodePort 來訪問 Kibana 這個服務,在瀏覽器中打開http://<任意節點IP>:31816即可,如果看到如下歡迎界面證明 Kibana 已經成功部署到了 Kubernetes集群之中。

kibana welcomekibana welcome

部署 Fluentd

Fluentd 是一個高效的日志聚合器,是用 Ruby 編寫的,並且可以很好地擴展。對於大部分企業來說,Fluentd 足夠高效並且消耗的資源相對較少,另外一個工具Fluent-bit更輕量級,占用資源更少,但是插件相對 Fluentd 來說不夠豐富,所以整體來說,Fluentd 更加成熟,使用更加廣泛,所以我們這里也同樣使用 Fluentd 來作為日志收集工具。

工作原理

Fluentd 通過一組給定的數據源抓取日志數據,處理后(轉換成結構化的數據格式)將它們轉發給其他服務,比如 Elasticsearch、對象存儲等等。Fluentd 支持超過300個日志存儲和分析服務,所以在這方面是非常靈活的。主要運行步驟如下:

  • 首先 Fluentd 從多個日志源獲取數據
  • 結構化並且標記這些數據
  • 然后根據匹配的標簽將數據發送到多個目標服務去

fluentd 架構fluentd 架構

配置

一般來說我們是通過一個配置文件來告訴 Fluentd 如何采集、處理數據的,下面簡單和大家介紹下 Fluentd 的配置方法。

日志源配置

比如我們這里為了收集 Kubernetes 節點上的所有容器日志,就需要做如下的日志源配置:

<source> @id fluentd-containers.log @type tail path /var/log/containers/*.log pos_file /var/log/fluentd-containers.log.pos time_format %Y-%m-%dT%H:%M:%S.%NZ tag raw.kubernetes.* format json read_from_head true </source> 

上面配置部分參數說明如下:

  • id:表示引用該日志源的唯一標識符,該標識可用於進一步過濾和路由結構化日志數據
  • type:Fluentd 內置的指令,tail表示 Fluentd 從上次讀取的位置通過 tail 不斷獲取數據,另外一個是http表示通過一個 GET 請求來收集數據。
  • path:tail類型下的特定參數,告訴 Fluentd 采集/var/log/containers目錄下的所有日志,這是 docker 在 Kubernetes 節點上用來存儲運行容器 stdout 輸出日志數據的目錄。
  • pos_file:檢查點,如果 Fluentd 程序重新啟動了,它將使用此文件中的位置來恢復日志數據收集。
  • tag:用來將日志源與目標或者過濾器匹配的自定義字符串,Fluentd 匹配源/目標標簽來路由日志數據。

路由配置

上面是日志源的配置,接下來看看如何將日志數據發送到 Elasticsearch:

<match **> @id elasticsearch @type elasticsearch @log_level info include_tag_key true type_name fluentd host "#{ENV['OUTPUT_HOST']}" port "#{ENV['OUTPUT_PORT']}" logstash_format true <buffer> @type file path /var/log/fluentd-buffers/kubernetes.system.buffer flush_mode interval retry_type exponential_backoff flush_thread_count 2 flush_interval 5s retry_forever retry_max_interval 30 chunk_limit_size "#{ENV['OUTPUT_BUFFER_CHUNK_LIMIT']}" queue_limit_length "#{ENV['OUTPUT_BUFFER_QUEUE_LIMIT']}" overflow_action block </buffer> 
  • match:標識一個目標標簽,后面是一個匹配日志源的正則表達式,我們這里想要捕獲所有的日志並將它們發送給 Elasticsearch,所以需要配置成**
  • id:目標的一個唯一標識符。
  • type:支持的輸出插件標識符,我們這里要輸出到 Elasticsearch,所以配置成 elasticsearch,這是 Fluentd 的一個內置插件。
  • log_level:指定要捕獲的日志級別,我們這里配置成info,表示任何該級別或者該級別以上(INFO、WARNING、ERROR)的日志都將被路由到 Elsasticsearch。
  • host/port:定義 Elasticsearch 的地址,也可以配置認證信息,我們的 Elasticsearch 不需要認證,所以這里直接指定 host 和 port 即可。
  • logstash_format:Elasticsearch 服務對日志數據構建反向索引進行搜索,將 logstash_format 設置為true,Fluentd 將會以 logstash 格式來轉發結構化的日志數據。
  • Buffer: Fluentd 允許在目標不可用時進行緩存,比如,如果網絡出現故障或者 Elasticsearch 不可用的時候。緩沖區配置也有助於降低磁盤的 IO。

安裝

要收集 Kubernetes 集群的日志,直接用 DasemonSet 控制器來部署 Fluentd 應用,這樣,它就可以從 Kubernetes 節點上采集日志,確保在集群中的每個節點上始終運行一個 Fluentd 容器。當然可以直接使用 Helm 來進行一鍵安裝,為了能夠了解更多實現細節,我們這里還是采用手動方法來進行安裝。

首先,我們通過 ConfigMap 對象來指定 Fluentd 配置文件,新建 fluentd-configmap.yaml 文件,文件內容如下:

kind: ConfigMap apiVersion: v1 metadata: name: fluentd-config namespace: logging labels: addonmanager.kubernetes.io/mode: Reconcile data: system.conf: |- <system> root_dir /tmp/fluentd-buffers/ </system> containers.input.conf: |- <source> @id fluentd-containers.log @type tail path /var/log/containers/*.log pos_file /var/log/es-containers.log.pos time_format %Y-%m-%dT%H:%M:%S.%NZ localtime tag raw.kubernetes.* format json read_from_head true </source> # Detect exceptions in the log output and forward them as one log entry. <match raw.kubernetes.**> @id raw.kubernetes @type detect_exceptions remove_tag_prefix raw message log stream stream multiline_flush_interval 5 max_bytes 500000 max_lines 1000 </match> system.input.conf: |- # Logs from systemd-journal for interesting services. <source> @id journald-docker @type systemd filters [{ "_SYSTEMD_UNIT": "docker.service" }] <storage> @type local persistent true </storage> read_from_head true tag docker </source> <source> @id journald-kubelet @type systemd filters [{ "_SYSTEMD_UNIT": "kubelet.service" }] <storage> @type local persistent true </storage> read_from_head true tag kubelet </source> forward.input.conf: |- # Takes the messages sent over TCP <source> @type forward </source> output.conf: |- # Enriches records with Kubernetes metadata <filter kubernetes.**> @type kubernetes_metadata </filter> <match **> @id elasticsearch @type elasticsearch @log_level info include_tag_key true host elasticsearch port 9200 logstash_format true request_timeout 30s <buffer> @type file path /var/log/fluentd-buffers/kubernetes.system.buffer flush_mode interval retry_type exponential_backoff flush_thread_count 2 flush_interval 5s retry_forever retry_max_interval 30 chunk_limit_size 2M queue_limit_length 8 overflow_action block </buffer> </match> 

上面配置文件中我們配置了 docker 容器日志目錄以及 docker、kubelet 應用的日志的收集,收集到數據經過處理后發送到 elasticsearch:9200 服務。

然后新建一個 fluentd-daemonset.yaml 的文件,文件內容如下:

apiVersion: v1 kind: ServiceAccount metadata: name: fluentd-es namespace: logging labels: k8s-app: fluentd-es kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: fluentd-es labels: k8s-app: fluentd-es kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile rules: - apiGroups: - "" resources: - "namespaces" - "pods" verbs: - "get" - "watch" - "list" --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: fluentd-es labels: k8s-app: fluentd-es kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile subjects: - kind: ServiceAccount name: fluentd-es namespace: logging apiGroup: "" roleRef: kind: ClusterRole name: fluentd-es apiGroup: "" --- apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd-es namespace: logging labels: k8s-app: fluentd-es version: v2.0.4 kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile spec: selector: matchLabels: k8s-app: fluentd-es version: v2.0.4 template: metadata: labels: k8s-app: fluentd-es kubernetes.io/cluster-service: "true" version: v2.0.4 # This annotation ensures that fluentd does not get evicted if the node # supports critical pod annotation based priority scheme. # Note that this does not guarantee admission on the nodes (#40573). annotations: scheduler.alpha.kubernetes.io/critical-pod: '' spec: serviceAccountName: fluentd-es containers: - name: fluentd-es image: cnych/fluentd-elasticsearch:v2.0.4 env: - name: FLUENTD_ARGS value: --no-supervisor -q resources: limits: memory: 500Mi requests: cpu: 100m memory: 200Mi volumeMounts: - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /data/docker/containers readOnly: true - name: config-volume mountPath: /etc/fluent/config.d nodeSelector: beta.kubernetes.io/fluentd-ds-ready: "true" tolerations: - key: node-role.kubernetes.io/master operator: Exists effect: NoSchedule terminationGracePeriodSeconds: 30 volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /data/docker/containers - name: config-volume configMap: name: fluentd-config 

我們將上面創建的 fluentd-config 這個 ConfigMap 對象通過 volumes 掛載到了 Fluentd 容器中,另外為了能夠靈活控制哪些節點的日志可以被收集,所以我們這里還添加了一個 nodSelector 屬性:

nodeSelector: beta.kubernetes.io/fluentd-ds-ready: "true" 

意思就是要想采集節點的日志,那么我們就需要給節點打上上面的標簽,比如我們這里3個節點都打上了該標簽:

$ kubectl get nodes --show-labels
NAME      STATUS    ROLES     AGE       VERSION   LABELS
master    Ready     master    245d      v1.10.0   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/fluentd-ds-ready=true,beta.kubernetes.io/os=linux,kubernetes.io/hostname=master,node-role.kubernetes.io/master= node02 Ready <none> 165d v1.10.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/fluentd-ds-ready=true,beta.kubernetes.io/os=linux,com=youdianzhishi,course=k8s,kubernetes.io/hostname=node02 node03 Ready <none> 225d v1.10.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/fluentd-ds-ready=true,beta.kubernetes.io/os=linux,jnlp=haimaxy,kubernetes.io/hostname=node03 

另外由於我們的集群使用的是 kubeadm 搭建的,默認情況下 master 節點有污點,所以要想也收集 master 節點的日志,則需要添加上容忍:

tolerations: - key: node-role.kubernetes.io/master operator: Exists effect: NoSchedule 

另外需要注意的地方是,我這里的測試環境更改了 docker 的根目錄:

$ docker info
... Docker Root Dir: /data/docker ... 

所以上面要獲取 docker 的容器目錄需要更改成/data/docker/containers,這個地方非常重要,當然如果你沒有更改 docker 根目錄則使用默認的/var/lib/docker/containers目錄即可。

分別創建上面的 ConfigMap 對象和 DaemonSet:

$ kubectl create -f fluentd-configmap.yaml
configmap "fluentd-config" created $ kubectl create -f fluentd-daemonset.yaml serviceaccount "fluentd-es" created clusterrole.rbac.authorization.k8s.io "fluentd-es" created clusterrolebinding.rbac.authorization.k8s.io "fluentd-es" created daemonset.apps "fluentd-es" created 

創建完成后,查看對應的 Pods 列表,檢查是否部署成功:

$ kubectl get pods -n logging
NAME                      READY     STATUS    RESTARTS   AGE
es-cluster-0              1/1       Running   0          1d
es-cluster-1              1/1       Running   0          1d
es-cluster-2              1/1       Running   0          1d
fluentd-es-2z9jg          1/1       Running   1          35s
fluentd-es-6dfdd          1/1       Running   0          35s
fluentd-es-bfkg7          1/1       Running   0          35s
kibana-7558d4dc4d-5mqdz   1/1       Running   0          1d

Fluentd 啟動成功后,我們可以前往 Kibana 的 Dashboard 頁面中,點擊左側的Discover,可以看到如下配置頁面:

create indexcreate index

在這里可以配置我們需要的 Elasticsearch 索引,前面 Fluentd 配置文件中我們采集的日志使用的是 logstash 格式,這里只需要在文本框中輸入logstash-*即可匹配到 Elasticsearch 集群中的所有日志數據,然后點擊下一步,進入以下頁面:

index configindex config

在該頁面中配置使用哪個字段按時間過濾日志數據,在下拉列表中,選擇@timestamp字段,然后點擊Create index pattern,創建完成后,點擊左側導航菜單中的Discover,然后就可以看到一些直方圖和最近采集到的日志數據了:

log datalog data

測試

現在我們來將上一節課的計數器應用部署到集群中,並在 Kibana 中來查找該日志數據。

新建 counter.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'] 

該 Pod 只是簡單將日志信息打印到 stdout,所以正常來說 Fluentd 會收集到這個日志數據,在 Kibana 中也就可以找到對應的日志數據了,使用 kubectl 工具創建該 Pod:

$ kubectl create -f counter.yaml

Pod 創建並運行后,回到 Kibana Dashboard 頁面,在上面的Discover頁面搜索欄中輸入kubernetes.pod_name:counter,就可以過濾 Pod 名為 counter 的日志數據:

counter log datacounter log data

我們也可以通過其他元數據來過濾日志數據,比如 您可以單擊任何日志條目以查看其他元數據,如容器名稱,Kubernetes 節點,命名空間等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM