Kubernetes 常用日志收集方案


Kubernetes 常用日志收集方案

學習了 Kubernetes 集群中監控系統的搭建,除了對集群的監控報警之外,還有一項運維工作是非常重要的,那就是日志的收集。

介紹

應用程序和系統日志可以幫助我們了解集群內部的運行情況,日志對於我們調試問題和監視集群情況也是非常有用的。而且大部分的應用都會有日志記錄,對於傳統的應用大部分都會寫入到本地的日志文件之中。對於容器化應用程序來說則更簡單,只需要將日志信息寫入到 stdout 和 stderr 即可,容器默認情況下就會把這些日志輸出到宿主機上的一個 JSON 文件之中,同樣我們也可以通過 docker logs 或者 kubectl logs 來查看到對應的日志信息。

但是,通常來說容器引擎或運行時提供的功能不足以記錄完整的日志信息,比如,如果容器崩潰了、Pod 被驅逐了或者節點掛掉了,我們仍然也希望訪問應用程序的日志。所以,日志應該獨立於節點、Pod 或容器的生命周期,這種設計方式被稱為 cluster-level-logging,即完全獨立於 Kubernetes 系統,需要自己提供單獨的日志后端存儲、分析和查詢工具。

Kubernetes 中的基本日志

下面這個示例是 Kubernetes 中的一個基本日志記錄的示例,直接將數據輸出到標准輸出流,如下:

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']

將上面文件保存為 counter-pod.yaml,該 Pod 每秒輸出一些文本信息,創建這個 Pod:

$ kubectl create -f counter-pod.yaml pod "counter" created

創建完成后,可以使用 kubectl logs 命令查看日志信息:

$ kubectl logs counter
Thu Dec 27 15:47:04 UTC 2020
Thu Dec 27 15:47:05 UTC 2020
Thu Dec 27 15:47:06 UTC 2020
Thu Dec 27 15:47:07 UTC 2020
......

Kubernetes 日志收集

Kubernetes 集群本身不提供日志收集的解決方案,一般來說有主要的3種方案來做日志收集:

  • 在節點上運行一個 agent 來收集日志
  • 在 Pod 中包含一個 sidecar 容器來收集應用日志
  • 直接在應用程序中將日志信息推送到采集后端

節點日志采集代理

通過在每個節點上運行一個日志收集的 agent 來采集日志數據,日志采集 agent 是一種專用工具,用於將日志數據推送到統一的后端。一般來說,這種 agent 用一個容器來運行,可以訪問該節點上所有應用程序容器的日志文件所在目錄。

由於這種 agent 必須在每個節點上運行,所以直接使用 DaemonSet 控制器運行該應用程序即可。在節點上運行一個日志收集的 agent 這種方式是最常見的一直方法,因為它只需要在每個節點上運行一個代理程序,並不需要對節點上運行的應用程序進行更改,對應用程序沒有任何侵入性,但是這種方法也僅僅適用於收集輸出到 stdout 和 stderr 的應用程序日志。

容器化應用程序寫入的所有內容,stdout以及stderr由容器引擎處理和重定向的任何地方。例如,Docker容器引擎將這兩個流重定向到日志記錄驅動程序,該驅動程序在Kubernetes中配置為以json格式寫入文件。

注意: Docker json日志記錄驅動程序將每一行視為一條單獨的消息。使用Docker日志記錄驅動程序時,不直接支持多行消息。需要在日志記錄代理程序級別或更高級別處理多行消息。

默認情況下,如果容器重新啟動,則kubelet會保留一個終止的容器及其日志。如果將Pod從節點中逐出,則所有對應的容器及其日志也會被逐出。

節點日志記錄中的一個重要考慮因素是實現日志輪換,以使日志不會消耗節點上的所有可用存儲。Kubernetes目前不負責輪換日志,但是部署工具應該設置解決方案來解決該問題。例如,在由kube-up.sh腳本部署的Kubernetes集群中,有一個logrotate 配置為每小時運行一次的工具。您還可以設置容器運行時以自動輪換應用程序的日志,例如使用Docker的log-opt。在kube-up.sh腳本中,后一種方法用於GCP上的COS圖像,前一種方法用於任何其他環境。在這兩種情況下,默認情況下輪換配置為在日志文件超過10MB時進行。

例如,您可以kube-up.sh在相應的腳本中找到有關如何在GCP(Google計算機引擎)上設置COS映像日志記錄的詳細信息。

參考鏈接:https://www.kubernetes.org.cn/doc-8

kubectl logs按照基本日志記錄示例中的方式運行時,節點上的kubelet會處理請求並直接從日志文件中讀取,並返回響應中的內容。

注意:當前,如果某些外部系統執行了輪換,則只能通過訪問最新日志文件的內容 kubectl logs。例如,如果有一個10MB的文件,則logrotate執行輪換,並且有兩個文件(一個10MB,一個空白) kubectl logs將返回一個空響應。

系統組件日志

有兩種類型的系統組件:在容器中運行的系統組件和不在容器中運行的系統組件。例如:

  • Kubernetes調度程序和kube-proxy在容器中運行。
  • kubelet和容器運行時(例如Docker)不在容器中運行。

在具有systemd的計算機上,kubelet和容器運行時將寫入日志。如果systemd不存在,它們將寫入.log目錄中的/var/log文件。容器中的系統組件總是/var/log繞過默認的日志記錄機制寫入目錄。他們使用klog 日志記錄庫。您可以在有關日志記錄的開發文檔中找到這些組件的日志記錄嚴重性約定。

與容器日志類似,/var/log 目錄中的系統組件日志應輪換使用。在kube-up.sh腳本啟動的Kubernetes集群中,這些日志配置為logrotate每天或每當大小超過100MB 時由工具輪換。

以 sidecar 容器收集日志

我們看上面的圖可以看到有一個明顯的問題就是我們采集的日志都是通過輸出到容器的 stdout 和 stderr 里面的信息,這些信息會在本地的容器對應目錄中保留成 JSON 日志文件,所以直接在節點上運行一個 agent 就可以采集到日志。但是如果我們的應用程序的日志是輸出到容器中的某個日志文件的話呢?這種日志數據顯然只通過上面的方案是采集不到的了。

用 sidecar 容器重新輸出日志

img

對於上面這種情況我們可以直接在 Pod 中啟動另外一個 sidecar 容器,直接將應用程序的日志通過這個容器重新輸出到 stdout,這樣是不是通過上面的節點日志收集方案又可以完成了。

由於這個 sidecar 容器的主要邏輯就是將應用程序中的日志進行重定向打印,所以背后的邏輯非常簡單,開銷很小,而且由於輸出到了 stdout 或者 stderr,所以我們也可以使用 kubectl logs 來查看日志了。

下面的示例是在 Pod 中將日志記錄在了容器的兩個本地文件之中:

two-files-counter-pod.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)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  volumes:
  - name: varlog
    emptyDir: {}

由於 Pod 中容器的特性,我們可以利用另外一個 sidecar 容器去獲取到另外容器中的日志文件,然后將日志重定向到自己的 stdout 流中,可以將上面的 YAML 文件做如下修改:(two-files-counter-pod-streaming-sidecar.yaml)

two-files-counter-pod-streaming-sidecar.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)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-log-1
    image: busybox
    args: [/bin/sh, -c, 'tail -n+1 -f /var/log/1.log']
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-log-2
    image: busybox
    args: [/bin/sh, -c, 'tail -n+1 -f /var/log/2.log']
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  volumes:
  - name: varlog
    emptyDir: {}

直接創建上面的 Pod:

$ kubectl create -f two-files-counter-pod-streaming-sidecar.yaml pod "counter" created

運行成功后,我們可以通過下面的命令來查看日志的信息:

kubectl logs counter count-log-1
0: Mon Jan  1 00:00:00 UTC 2001
1: Mon Jan  1 00:00:01 UTC 2001
2: Mon Jan  1 00:00:02 UTC 2001
...
kubectl logs counter count-log-2
Mon Jan  1 00:00:00 UTC 2001 INFO 0
Mon Jan  1 00:00:01 UTC 2001 INFO 1
Mon Jan  1 00:00:02 UTC 2001 INFO 2
...

這樣前面節點上的日志采集 agent 就可以自動獲取這些日志信息,而不需要其他配置。

這種方法雖然可以解決上面的問題,但是也有一個明顯的缺陷,就是日志不僅會在原容器文件中保留下來,還會通過 stdout 輸出后占用磁盤空間,這樣無形中就增加了一倍磁盤空間。

使用 sidecar 運行日志采集 agent(fluentd插件)

如果你覺得在節點上運行一個日志采集的代理不夠靈活的話,那么你也可以創建一個單獨的日志采集代理程序的 sidecar 容器,不過需要單獨配置和應用程序一起運行。

不過這樣雖然更加靈活,但是在 sidecar 容器中運行日志采集代理程序會導致大量資源消耗,因為你有多少個要采集的 Pod,就需要運行多少個采集代理程序,另外還無法使用 kubectl logs 命令來訪問這些日志,因為它們不受 kubelet 控制。

舉個例子,你可以使用的Stackdriver,它使用fluentd作為記錄劑。以下是兩個可用於實現此方法的配置文件。第一個文件包含配置流利的ConfigMap。

下面是 Kubernetes 官方的一個 fluentd 的配置文件示例,使用 ConfigMap 對象來保存:

fluentd-sidecar-config.yaml 

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
data:
  fluentd.conf: |
    <source>
      type tail
      format none
      path /var/log/1.log
      pos_file /var/log/1.log.pos
      tag count.format1
    </source>

    <source>
      type tail
      format none
      path /var/log/2.log
      pos_file /var/log/2.log.pos
      tag count.format2
    </source>

    <match **>
      type google_cloud
    </match>

上面的配置文件是配置收集原文件 /var/log/1.log 和 /var/log/2.log 的日志數據,然后通過 google_cloud 這個插件將數據推送到 Stackdriver 后端去。

下面是我們使用上面的配置文件在應用程序中運行一個 fluentd 的容器來讀取日志數據:

two-files-counter-pod-agent-sidecar.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)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-agent
    image: k8s.gcr.io/fluentd-gcp:1.30
    env:
    - name: FLUENTD_ARGS
      value: -c /etc/fluentd-config/fluentd.conf
    volumeMounts:
    - name: varlog
      mountPath: /var/log
    - name: config-volume
      mountPath: /etc/fluentd-config
  volumes:
  - name: varlog
    emptyDir: {}
  - name: config-volume
    configMap:
      name: fluentd-config

上面的 Pod 創建完成后,容器 count-agent 就會將 count 容器中的日志進行收集然后上傳。當然,這只是一個簡單的示例,我們也完全可以使用其他的任何日志采集工具來替換 fluentd,比如 logstash、fluent-bit 等等。

直接從應用程序收集日志

除了上面的幾種方案之外,我們也完全可以通過直接在應用程序中去顯示的將日志推送到日志后端,但是這種方式需要代碼層面的實現,也超出了 Kubernetes 本身的范圍。

參考鏈接:https://kubernetes.io/docs/concepts/cluster-administration/logging/


免責聲明!

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



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