前面的課程中和大家一起學習了 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 2018
Thu Dec 27 15:47:05 UTC 2018
Thu Dec 27 15:47:06 UTC 2018
Thu Dec 27 15:47:07 UTC 2018
......
Kubernetes 日誌收集
Kubernetes 集羣本身不提供日誌收集的解決方案,一般來說有主要的3種方案來做日誌收集:
-
在節點上運行一個 agent 來收集日誌
-
在 Pod 中包含一個 sidecar 容器來收集應用日誌
-
直接在應用程序中將日誌信息推送到採集後端
節點日誌採集代理
通過在每個節點上運行一個日誌收集的 agent 來採集日誌數據,日誌採集 agent 是一種專用工具,用於將日誌數據推送到統一的後端。一般來說,這種 agent 用一個容器來運行,可以訪問該節點上所有應用程序容器的日誌文件所在目錄。
由於這種 agent 必須在每個節點上運行,所以直接使用 DaemonSet 控制器運行該應用程序即可。在節點上運行一個日誌收集的 agent 這種方式是最常見的一直方法,因爲它只需要在每個節點上運行一個代理程序,並不需要對節點上運行的應用程序進行更改,對應用程序沒有任何侵入性,但是這種方法也僅僅適用於收集輸出到 stdout 和 stderr 的應用程序日誌。
以 sidecar 容器收集日誌
我們看上面的圖可以看到有一個明顯的問題就是我們採集的日誌都是通過輸出到容器的 stdout 和 stderr 裏面的信息,這些信息會在本地的容器對應目錄中保留成 JSON 日誌文件,所以直接在節點上運行一個 agent 就可以採集到日誌。但是如果我們的應用程序的日誌是輸出到容器中的某個日誌文件的話呢?這種日誌數據顯然只通過上面的方案是採集不到的了。
用 sidecar 容器重新輸出日誌
對於上面這種情況我們可以直接在 Pod 中啓動另外一個 sidecar 容器,直接將應用程序的日誌通過這個容器重新輸出到 stdout,這樣是不是通過上面的節點日誌收集方案又可以完成了。
由於這個 sidecar 容器的主要邏輯就是將應用程序中的日誌進行重定向打印,所以背後的邏輯非常簡單,開銷很小,而且由於輸出到了 stdout 或者 stderr,所以我們也可以使用 kubectl logs 來查看日誌了。
下面的示例是在 Pod 中將日誌記錄在了容器的兩個本地文件之中:
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)
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
如果你覺得在節點上運行一個日誌採集的代理不夠靈活的話,那麼你也可以創建一個單獨的日誌採集代理程序的 sidecar 容器,不過需要單獨配置和應用程序一起運行。
不過這樣雖然更加靈活,但是在 sidecar 容器中運行日誌採集代理程序會導致大量資源消耗,因爲你有多少個要採集的 Pod,就需要運行多少個採集代理程序,另外還無法使用 kubectl logs 命令來訪問這些日誌,因爲它們不受 kubelet 控制。
舉個例子,你可以使用的Stackdriver,它使用fluentd作爲記錄劑。以下是兩個可用於實現此方法的配置文件。第一個文件包含配置流利的ConfigMap。
下面是 Kubernetes 官方的一個 fluentd 的配置文件示例,使用 ConfigMap 對象來保存:
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 的容器來讀取日誌數據:
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/