如今,無法想像基於 Kubernetes 的專案沒有 ELK 堆疊,它保存叢集的應用程式和系統元件的日誌。 在我們的實踐中,我們將 EFK 堆疊與 Fluentd 一起使用,而不是 Logstash。
Fluentd 是一個現代的通用日誌收集器,越來越受歡迎,並已加入雲端原生運算基金會,這就是為什麼它的開發向量專注於與 Kubernetes 結合使用。
使用 Fluentd 代替 Logstash 的事實並沒有改變該軟體包的一般本質,但是,Fluentd 的特點是其多功能性所帶來的特定細微差別。
例如,當我們開始在一個日誌記錄強度較高的繁忙專案中使用 EFK 時,我們面臨這樣的事實:在 Kibana 中,某些訊息會重複顯示多次。 在這篇文章中我們將告訴您為什麼會出現這種現像以及如何解決問題。
文件重複的問題
在我們的專案中,Fluentd 部署為 DaemonSet(在 Kubernetes 叢集的每個節點上的一個實例中自動啟動)並監視 /var/log/containers 中的 stdout 容器日誌。 收集和處理後,日誌以JSON文件的形式傳送到ElasticSearch,根據專案的規模以及效能和容錯的要求,以叢集或單機的形式提出。 Kibana作為圖形介面。
當使用帶有輸出緩衝插件的 Fluentd 時,我們遇到了一種情況,ElasticSearch 中的某些文件內容完全相同,僅標識符不同。 您可以使用 Nginx 日誌作為範例來驗證這是否是訊息重複。 在日誌檔案中,此訊息存在於單一副本中:
127.0.0.1 192.168.0.1 - [28/Feb/2013:12:00:00 +0900] "GET / HTTP/1.1" 200 777 "-" "Opera/12.0" -
但是,ElasticSearch 中有多個文件包含此訊息:
{
"_index": "test-custom-prod-example-2020.01.02",
"_type": "_doc",
"_id": "HgGl_nIBR8C-2_33RlQV",
"_version": 1,
"_score": 0,
"_source": {
"service": "test-custom-prod-example",
"container_name": "nginx",
"namespace": "test-prod",
"@timestamp": "2020-01-14T05:29:47.599052886 00:00",
"log": "127.0.0.1 192.168.0.1 - [28/Feb/2013:12:00:00 0900] "GET / HTTP/1.1" 200 777 "-" "Opera/12.0" -",
"tag": "custom-log"
}
}
{
"_index": "test-custom-prod-example-2020.01.02",
"_type": "_doc",
"_id": "IgGm_nIBR8C-2_33e2ST",
"_version": 1,
"_score": 0,
"_source": {
"service": "test-custom-prod-example",
"container_name": "nginx",
"namespace": "test-prod",
"@timestamp": "2020-01-14T05:29:47.599052886 00:00",
"log": "127.0.0.1 192.168.0.1 - [28/Feb/2013:12:00:00 0900] "GET / HTTP/1.1" 200 777 "-" "Opera/12.0" -",
"tag": "custom-log"
}
}
而且,可以有兩次以上的重複。
在Fluentd日誌中修復此問題時,您可以看到大量警告,內容如下:
2020-01-16 01:46:46 +0000 [warn]: [test-prod] failed to flush the buffer. retry_time=4 next_retry_seconds=2020-01-16 01:46:53 +0000 chunk="59c37fc3fb320608692c352802b973ce" error_class=Fluent::Plugin::ElasticsearchOutput::RecoverableRequestFailure error="could not push logs to Elasticsearch cluster ({:host=>"elasticsearch", :port=>9200, :scheme=>"http", :user=>"elastic", :password=>"obfuscated"}): read timeout reached"
當 ElasticSearch 無法在 request_timeout 參數指定的時間內傳回對請求的回應時,就會出現這些警告,這就是無法清除轉送的緩衝區片段的原因。 之後,Fluentd 再次嘗試將緩衝區片段傳送到 ElasticSearch,並在任意次數的嘗試後,操作成功完成:
2020-01-16 01:47:05 +0000 [warn]: [test-prod] retry succeeded. chunk_id="59c37fc3fb320608692c352802b973ce"
2020-01-16 01:47:05 +0000 [warn]: [test-prod] retry succeeded. chunk_id="59c37fad241ab300518b936e27200747"
2020-01-16 01:47:05 +0000 [warn]: [test-dev] retry succeeded. chunk_id="59c37fc11f7ab707ca5de72a88321cc2"
2020-01-16 01:47:05 +0000 [warn]: [test-dev] retry succeeded. chunk_id="59c37fb5adb70c06e649d8c108318c9b"
2020-01-16 01:47:15 +0000 [warn]: [kube-system] retry succeeded. chunk_id="59c37f63a9046e6dff7e9987729be66f"
然而,ElasticSearch 將每個傳輸的緩衝區片段視為唯一,並在索引期間為它們分配唯一的 _id 欄位值。 這就是郵件副本的顯示方式。
在 Kibana 中它看起來像這樣:
解決方案
有多種選擇可以解決這個問題。 其中之一是 Fluent-plugin-elasticsearch 插件中內建的機制,用於為每個文件產生唯一的雜湊值。 如果使用此機制,ElasticSearch 將在轉送階段識別重複並防止重複文件。 但我們必須考慮到這種解決問題的方法與調查很困難,並且不能消除超時不足的錯誤,因此我們放棄了它的使用。
我們在 Fluentd 輸出上使用緩衝插件,以防止在出現短期網路問題或日誌記錄強度增加時發生日誌遺失。 如果由於某種原因 ElasticSearch 無法立即將文件寫入索引,則該文件將排隊並儲存在磁碟上。 因此,在我們的例子中,為了消除導致上述錯誤的問題根源,有必要為緩衝參數設定正確的值,此時 Fluentd 輸出緩衝區將具有足夠的大小並且同時設法在規定的時間內清除。
值得注意的是,下面討論的參數值在輸出插件中使用緩衝的每種特定情況下都是單獨的,因為它們取決於許多因素:服務將訊息寫入日誌的強度、磁碟系統效能、網路頻道負載及其頻寬。 因此,為了獲得適合每種情況的緩衝區設置,但又不冗餘,避免盲目冗長的搜索,可以利用Fluentd在運行過程中寫入日誌的調試信息,相對快速地獲得正確的值。
在記錄問題時,配置如下所示:
<buffer>
@type file
path /var/log/fluentd-buffers/kubernetes.test.buffer
flush_mode interval
retry_type exponential_backoff
flush_thread_count 2
flush_interval 5s
retry_forever
retry_max_interval 30
chunk_limit_size 8M
queue_limit_length 8
overflow_action block
</buffer>
求解問題時,手動選擇以下參數的值:
chunk_limit_size — 緩衝區中的訊息被分割成的區塊的大小。
- lush_interval — 清除緩衝區的時間間隔。
- queue_limit_length — 佇列中區塊的最大數量。
- request_timeout 是 Fluentd 和 ElasticSearch 之間建立連線的時間。
總緩衝區大小可以透過參數queue_limit_length和chunk_limit_size相乘來計算,它可以解釋為「佇列中區塊的最大數量,每個區塊都有給定的大小」。 如果緩衝區大小不足,日誌中會出現以下警告:
2020-01-21 10:22:57 +0000 [warn]: [test-prod] failed to write data into buffer by buffer overflow action=:block
意味著緩衝區沒有時間在規定的時間內被清除,進入滿緩衝區的資料被阻塞,這會導致部分日誌遺失。
您可以透過兩種方式增加緩衝區:增加佇列中每個區塊的大小,或增加佇列中可以包含的區塊的數量。
如果您將區塊大小 chunk_limit_size 設定為超過 32 MB,則 ElasticSeacrh 將不會接受它,因為傳入的封包太大。 因此,如果需要進一步增加緩衝區,最好增加最大佇列長度queue_limit_length。
當緩衝區停止溢位並且只剩下逾時不足訊息時,您可以開始增加 request_timeout 參數。 但是,如果將值設為超過 20 秒,Fluentd 日誌中將開始出現以下警告:
2020-01-21 09:55:33 +0000 [warn]: [test-dev] buffer flush took longer time than slow_flush_log_threshold: elapsed_time=20.85753920301795 slow_flush_log_threshold=20.0 plugin_id="postgresql-dev"
該訊息不會以任何方式影響系統的操作,並且意味著緩衝區刷新時間比slow_flush_log_threshold參數設定的時間長。 這是一個調試信息,我們在選擇 request_timeout 參數的值時使用它。
廣義選擇演算法如下:
- 將 request_timeout 設定為保證大於所需值(數百秒)的值。 在設定過程中,正確設定此參數的主要標準是缺少逾時的警告消失。
- 等待超過 Slow_flush_log_threshold 閾值的訊息。 elapsed_time 欄位中的警告文字將顯示緩衝區被清除的即時時間。
- 將 request_timeout 設定為大於觀察期間獲得的最大 elapsed_time 值。 我們將 request_timeout 值計算為 elapsed_time + 50%。
- 若要從日誌中刪除有關長緩衝區刷新的警告,可以提高 Slow_flush_log_threshold 的值。 我們將該值計算為 elapsed_time + 25%。
如前所述,這些參數的最終值是針對每種情況單獨獲得的。 透過遵循上述演算法,我們可以保證消除導致重複訊息的錯誤。
下表顯示了在選擇上述參數值的過程中每天導致訊息重複的錯誤數量如何變化:
節點1
節點2
節點3
節點4
之前之後
之前之後
之前之後
之前之後
刷新緩衝區失敗
1749/2
694/2
47/0
1121/2
重試成功
410/2
205/1
24/0
241/2
另外值得注意的是,隨著專案的成長,最終的設定可能會失去其相關性,相應地,日誌數量也會增加。 逾時不足的主要標誌是向 Fluentd 日誌傳回有關長緩衝區刷新的訊息,即超過 Slow_flush_log_threshold 閾值。 從此時開始,距離超過 request_timeout 參數還有很小的餘裕,因此需要及時回應這些訊息並重複上述選擇最佳設定的過程。
結論
微調 Fluentd 輸出緩衝區是配置 EFK 堆疊的主要階段之一,決定其操作的穩定性以及索引中文件的正確放置。 根據所描述的配置演算法,您可以確保所有日誌都將以正確的順序寫入ElasticSearch索引,而不會重複或遺失。
也請閱讀我們部落格上的其他文章:
Go 和 Zabbix 5.0 成為朋友 無需停機即可升級 Kubernetes 集群 Kubernetes:為什麼設定係統資源管理如此重要? 縮小 Docker 映像的三個簡單技巧 大量異質Web項目的備份
來源: www.habr.com