
如今,我們無法想像一個基於 Kubernetes 的專案沒有 ELK 堆疊,它儲存了應用程式和叢集系統元件的日誌。在我們的實踐中,我們使用帶有 Fluentd 的 EFK 堆疊而不是 Logstash。
Fluentd 是一個現代的、通用的日誌收集器,它越來越受歡迎,並且已經加入了雲端原生運算基金會,這就是為什麼它的開發向量專注於與 Kubernetes 結合使用。
使用 Fluentd 取代 Logstash 並不會改變該軟體包的整體本質,但是,Fluentd 具有其多功能性所帶來的特定細微差別。
例如,當我們在一個繁忙且日誌記錄強度較高的專案中開始使用 EFK 時,我們遇到了一些訊息在 Kibana 中重複顯示多次的情況。在本文中我們將告訴您為什麼會出現這種現像以及如何解決這個問題。
重複檔案的問題
在我們的專案中,Fluentd 被部署為 DaemonSet(在 Kubernetes 叢集的每個節點上自動執行一個實例)並監視 /var/log/containers 中的容器 stdout 日誌。日誌經過收集和處理後,以JSON文件的形式傳送到ElasticSearch,以叢集或獨立的形式提升,取決於專案的規模和對效能和容錯的要求。 Kibana 用作圖形介面。
當使用帶有輸出緩衝插件的 Fluentd 時,我們遇到了這樣一種情況:ElasticSearch 中的一些文件具有完全相同的內容,只是 ID 有所不同。您可以使用 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 — 緩衝區中訊息被分成的區塊的大小。
- flush_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 兆位元組,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 索引,而不會重複或遺失。
也請閱讀我們部落格上的其他文章:
來源: www.habr.com
