Monitoring GKE With Elk
2020 It邦幫忙鐵人賽 系列文章
- Self-host ELK stack on GCP
- Secure ELK Stask
- 監測 Google Compute Engine 上服務的各項數據
- 監測 Google Kubernetes Engine 的各項數據
- 是否選擇 ELK 作為解決方案
- 使用 logstash pipeline 做數據前處理
- Elasticsearch 日常維護:數據清理,效能調校,永久儲存
- Debug ELK stack on GCP
作為範例的 ELK 的版本是當前的 stable release 7.3.1。
由於我比較熟悉 GCP / GKE 的服務,這篇的操作過程都會以 GCP 平台作為範例,不過操作過程大體上是跨平台通用的。
這篇來要 Kubernetes 環境(GKE)裡面的 log 抓出來,送到 ELK 上。
官方文件 ,寫得很簡易,如果已經很熟 kubernetes 的人可以直接腦補其他的部屬設定。
這邊有幾個做法,依照 filebeat 部署的位置與收集目標簡單分為:
- node: 處理每一台 node 的 log ,包含 system log 與 node 監測資料(metrics)
- cluster: 處理 cluster 等級的 log, event 或是 metrics
- pod: 針對特定 pod 直接去掛一個 sidecar
上面的方法是可以混搭的,kubernetes 個個層級有log 處理流程,我們這邊把 log 送往第三方平台,也是需要依照原本的 log 流程,去收取我們想收集的 log。
簡單來說,是去對的地方找對的 log。在架構上要注意 scalability 與 resource 分配,不要影響本身提供服務的 GKE ,但又能獲得盡量即時的 log。
我們這邊直接進入 kubernetes resource 的設定,底下會附上在 GKE 找 log 的過程。
Node level log harvest
為每一個 node 配置 filebeat,然後在 node 上面尋找 log,然後如我們上篇所敘述加到 input ,就可以把 log 倒出來。
直覺想到就是透過 daemonsets 為每個 node 部署一個 filebeat pod,然後 mount node 的 log 資料夾,在設置 input。
Deploy daemonsets
kubernetes resource 的 yaml 請參考 我的 github elk-kubernetes
給予足夠的 clusterrolebinding 到 elk
1kubectl apply -f filebeat/7.3.1/clusterrolebinding.yaml
先更改 filebeat 的設定,如何設定 elasticsearch 與 kibana,請參考上篇。至於 input 的部份已經配置好了。
1vim filebeat/7.3.1/daemonsets-config-configmap.yaml
2
3kubectl apply -f filebeat/7.3.1/daemonsets-config-configmap.yaml
部屬 filebeat daemonsets,會每一個 node 部屬一個 filebeat
1kubectl apply -f filebeat/7.3.1/daemonsets.yaml
取得 daemonsets 的狀態
1kubectl --namespcae elk get pods
2
3NAME READY STATUS RESTARTS AGE
4filebeat-bjfp9 1/1 Running 0 6m56s
5filebeat-fzr9n 1/1 Running 0 6m56s
6filebeat-vpkm7 1/1 Running 0 6m56s
7...
有設定成功的話,kibana 這邊就會收到 kubernetes 上面 pod 的 log
log havest for specific pods
由於 kubernetes 上我們可以便利的調度 filebeat 的部屬方式,這邊也可以也可以使用 deployment ,配合 pod affinity,把 filebeat 放到某個想要監測的 pod,這邊的例子是 nginx-ingress-controller。
- Kubernetes 上有一個或多個 nginx ingress controller
- 部屬一個或多個 filebeat 到有 nginx 的 node 上
- filebeat 去抓取 nginx 的 input, 並使用 filebeat 的 nginx module 做預處理
- nginx module 預設路徑需要調整,這邊使用 filebeat autodiscover 來處理
一樣 apply 前記得先檢查跟設定
1vim filebeat/7.3.1/nginx-config-configmap.yaml
2
3kubectl apply -f filebeat/7.3.1/nginx-config-configmap.yaml
部屬 filebeat deployment 由於有設定 pod affinity ,這個 filebeat 只會被放到有 nginx ingress controller 的這個節點上,並且依照 autodiscover 設定的條件去蒐集 nginx 的 log
1kubectl apply -f filebeat/7.3.1/nginx-deployment.yaml
有設定成功的話,kibana 這邊就會收到 kubernetes 上面 pod 的 log
另外,由於有啟動 nginx module,logstash 收到的內容已經是處理過得內容。
GCP fluentd
如果是使用 GKE 的朋友,可以投過開啟 stackdriver logging 的功能,把集群中服務的 log 倒到 stackdriver,基本上就是 node -> (daemonsets) fluentd -> stackdriver。
這個 fluentd 是 GCP 如果有啟動 Stackdriver Logging 的話,自動幫你維護的 daemonsets,設定不可改,改了會被 overwrite 會去,所以不太方便從這邊動手腳。
Btw stackdriver 最近好像改版,目前做 example 的版本已經變成 lagency (淚
但我們先假設我們對這個 pod 的 log 很有興趣,然後把這邊的 log 透過 filebeat 送到 ELK 上XD
因為 GKE 透過 fluentd 把 GKE 上面的 log 倒到 stackdriver,而我們是想把 log 倒到 ELK,既然這樣我們的 input 來源是相同的,而且很多處理步驟都可以在 ELK 上面互通,真的可以偷看一下 fluentd 是去哪收集 log ,怎麼處理 log pipeline,我們只要做相應設定就好。
畢竟 google 都幫我們弄得妥妥的,不參考一下他的流程太可惜。
偷看一下 GKE 上 fluentd 是去哪找 log ,這個是 fluentd gcp configmap,雖然看到這邊感覺扯遠了,但因為很有趣所有我就繼續看下去,各位大德可以跳過XD
configmap 中的這個 input 設定檔,其中一個 source 就是一個資料來源,相當於 filebeat 的 input。這邊這個 source 就是去 /var/log/containers/*.log
收 log
這邊還做了幾件事:
- 打上
reform.*
tag,讓下個 match 可以 收進去 pipeline 處理 - 附帶 parse 出 time
1containers.input.conf
2
3<source>
4 @type tail
5 path /var/log/containers/*.log
6 pos_file /var/log/gcp-containers.log.pos
7 # Tags at this point are in the format of:
8 # reform.var.log.containers.<POD_NAME>_<NAMESPACE_NAME>_<CONTAINER_NAME>-<CONTAINER_ID>.log
9 tag reform.*
10 read_from_head true
11 <parse>
12 @type multi_format
13 <pattern>
14 format json
15 time_key time
16 time_format %Y-%m-%dT%H:%M:%S.%NZ
17 </pattern>
18 <pattern>
19 format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/
20 time_format %Y-%m-%dT%H:%M:%S.%N%:z
21 </pattern>
22 </parse>
23</source>
他這邊做一些 error handling,然後用 ruby (!) parse,這邊就真的太遠,細節大家可以 google XD。不過這邊使用的 pattern matching 我們後幾篇在 logstash pipeline 上,也會有機會提到,機制是類似的。
1<filter reform.**>
2 @type parser
3 format /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<log>.*)/
4 reserve_data true
5 suppress_parse_error_log true
6 emit_invalid_record_to_error false
7 key_name log
8</filter>
9
10<match reform.**>
11 @type record_reformer
12 enable_ruby true
13 <record>
14 # Extract local_resource_id from tag for 'k8s_container' monitored
15 # resource. The format is:
16 # 'k8s_container.<namespace_name>.<pod_name>.<container_name>'.
17 "logging.googleapis.com/local_resource_id" ${"k8s_container.#{tag_suffix[4].rpartition('.')[0].split('_')[1]}.#{tag_suffix[4].rpartition('.')[0].split('_')[0]}.#{tag_suffix[4].rpartition('.')[0].split('_')[2].rpartition('-')[0]}"}
18 # Rename the field 'log' to a more generic field 'message'. This way the
19 # fluent-plugin-google-cloud knows to flatten the field as textPayload
20 # instead of jsonPayload after extracting 'time', 'severity' and
21 # 'stream' from the record.
22 message ${record['log']}
23 # If 'severity' is not set, assume stderr is ERROR and stdout is INFO.
24 severity ${record['severity'] || if record['stream'] == 'stderr' then 'ERROR' else 'INFO' end}
25 </record>
26 tag ${if record['stream'] == 'stderr' then 'raw.stderr' else 'raw.stdout' end}
27 remove_keys stream,log
28</match>
ssh 進去逛
想看機器上實際的 log 狀況,我們也可以直接 ssh 進去
先透過 kubectl 看一下 pod
1$ kubectl get daemonsets --namespace kube-system
2
3NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
4fluentd-gcp-v3.2.0 7 7 7 7 7 beta.kubernetes.io/fluentd-ds-ready=true 196d
5
6$ kubectl get pods --output wide --namespace kube-system
7
8NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
9fluentd-gcp-scaler-1234567890-vfbhc 1/1 Running 0 37d 10.140.0. gke-chechiachang-pool-1-123456789-5gqn <none> <none>
10fluentd-gcp-v3.2.0-44tl7 2/2 Running 0 37d 10.140.0. gke-chechiachang-pool-1-123456789-wcq0 <none> <none>
11fluentd-gcp-v3.2.0-5vc6l 2/2 Running 0 37d 10.140.0. gke-chechiachang-pool-1-123456789-tp05 <none> <none>
12fluentd-gcp-v3.2.0-6rqvc 2/2 Running 0 37d 10.140.0. gke-chechiachang-pool-1-123456789-5gqn <none> <none>
13fluentd-gcp-v3.2.0-mmwk4 2/2 Running 0 37d 10.140.0. gke-chechiachang-pool-1-123456789-vxld <none> <none>
先透過 kubectl 看一下 node
1$ kubectl get node
2
3NAME STATUS ROLES AGE VERSION
4gke-chechaichang-pool-1-123456789-3bzp Ready <none> 37d v1.13.7-gke.8
5gke-chechaichang-pool-1-123456789-5gqn Ready <none> 37d v1.13.7-gke.8
6gke-chechaichang-pool-1-123456789-8n8z Ready <none> 37d v1.13.7-gke.8
7...
8
9gcloud compute ssh gke-chechaichang-pool-1-123456789-3bzp
如使用其他雲平台的 kubernetes service,或是 bare metal 的集群,請依照各自系統的方式連進去看看。
ssh node 找 log
ssh 進去後就可以到處來探險,順便看看 GKE 跑在機器上到底做了什麼事情。
如果官方有出文件,可能可以不用進來看。各位大德有發現文件請留言跟我說。我個人很喜歡自己架集群起來連就去看,面對照官方文件上寫的東西,當然大部份時候都是文件沒有帶到,有很多發現。
1$ ls /var/log
2
3gcp-*-log.pos
4kube-proxy.log
5containers/
6metrics/
7pods/
8...
/var/log/containers 看一下,格式是 pod_namespace_container
這邊是 link 到 /var/log/pods/
1$ ls -al /var/log/containers
2
3lrwxrwxrwx 1 root root 105 Aug 12 07:42 fluentd-gcp-v3.2.0-st6cl_kube-system_fluentd-gcp-5e38c9b63c8d767091b122a9aa48c576a88cc20b4470d9ca18a820afa5c168ac.log -> /var/log/pods/kube-system_fluentd-gcp-v3.2.0-st6cl_b76bed0b-bcd4-11e9-a55c-42010a8c0008/fluentd-gcp/0.log
看到 pods 就覺得是你了,裡面有 pod 資料夾,格式是 namespace_pod_uuid
1ls /var/log/pods
2
3default_pod-1-1234567890-fxxhp_uuid
4kube-system_fluentd-gcp-v3.2.0-st6cl_b76bed0b-bcd4-11e9-a55c-42010a8c0008
5kube-system_heapster-v1.6.0-beta.1-
6kube-system_kube-proxy-gke-
7kube-system_l7-default-backend-
8kube-system_prometheus-to-sd-
再進去有 container log,格式是 pod_namespace_container.log
,也是 link
1ls -al /var/log/pods/kube-system_fluentd-gcp-v3.2.0-st6cl_b76bed0b-bcd4-11e9-a55c-42010a8c0008/fluentd-gcp/
2
3lrwxrwxrwx 1 root root 165 Aug 12 07:42 0.log -> /var/lib/docker/containers/5e38c9b63c8d767091b122a9aa48c576a88cc20b4470d9ca18a820afa5c168ac/5e38c9b63c8d767091b122a9aa48c576a88cc20b4470d9ca18a820afa5c168ac-json.log
最終 link 到
1sudo su
2
3$ ls -alh /var/lib/docker/containers/5e38c9b63c8d767091b122a9aa48c576a88cc20b4470d9ca18a820afa5c168ac/
4total 3.9M
5drwx------ 4 root root 4.0K Aug 12 07:42 .
6drwx------ 92 root root 20K Sep 18 11:28 ..
7-rw-r----- 1 root root 3.8M Sep 18 11:29 5e38c9b63c8d767091b122a9aa48c576a88cc20b4470d9ca18a820afa5c168ac-json.log
8drwx------ 2 root root 4.0K Aug 12 07:42 checkpoints
9-rw------- 1 root root 7.8K Aug 12 07:42 config.v2.json
10-rw-r--r-- 1 root root 2.3K Aug 12 07:42 hostconfig.json
11drwx------ 2 root root 4.0K Aug 12 07:42 mounts
頭尾偷喵一下,確定是我們在找的東西
1head /var/lib/docker/containers/5e38c9b63c8d767091b122a9aa48c576a88cc20b4470d9ca18a820afa5c168ac/5e38c9b63c8d767091b122a9aa48c576a88cc20b4470d9ca18a820afa5c168ac-json.log
2tail /var/lib/docker/containers/5e38c9b63c8d767091b122a9aa48c576a88cc20b4470d9ca18a820afa5c168ac/5e38c9b63c8d767091b122a9aa48c576a88cc20b4470d9ca18a820afa5c168ac-json.log
這樣就找到我們的 log 了
小節
- 使用 filebeat 去查找
- 透過 kubernetes daemonsets 可以快速佈置一份 filebeat 到所有 node,且設定都是一起更新
- 透過 kubernetes deployment 可以指定 filebeat 的位置,去跟隨想要監測的服務
- 如果不熟 log 處理流程,可以直接看偷看大廠的服務,會有很多靈感
- 沒事可以多跑進 Kubernetes 服務節點逛逛,有很多有趣的東西