日誌架構演進:從集中式到分佈式的Kubernetes日誌策略

當我們沒有使用雲原生方案部署應用時採用的日誌方案往往是 ELK 技術棧。

這套技術方案比較成熟,穩定性也很高,所以幾乎成爲了當時的標配。

可是隨着我們使用 kubernetes 步入雲原生的時代後, kubernetes 把以往的操作系統上的許多底層都屏蔽,再由他提供了一些標準接口。

同時在 kubernetes 中的日誌來源也比傳統虛擬機多,比如可能有容器、kubernetes 自身的事件、日誌等。

我們的日誌採集方案也得與時俱進,kubernetes 的官方博客有介紹提供一下幾種方案:

節點採集

第一種方案是在節點中採集日誌,我們知道 kubernetes 是分爲 master 調度節點以及 worker 工作節點;我們的應用都是運行在 worker 節點中的。

在 kubernetes 環境中更推薦使用標準的 stdout/stderr 作爲日誌輸出,這樣 kubernetes 更方便做統一處理。

以我們的 docker 運行時爲例,默認情況下我們的標準輸入文件會寫入到 /var/log 目錄中。

如上圖所示:我們可以在 kubernetes 的每一個 worker 節點中部署一個 DaemonSet 類型的採集器(filebeat 等),再由他去採集該節點下 /var/log 的日誌,最終由他將日誌採集後發往日誌處理的後端,比如 elasticsearch 等組件中。

這種方案的好處是資源佔用較低,往往是有多少個 worker 節點就可以部署多少個採集器。

而且和業務的耦合度低,業務和採集器不管誰進行重啓或升級互相都不會產生影響。

但缺點也比較明顯,整個節點的日誌採集瓶頸都在這個採集器這裏了,如果某些 worker 節點的 Pod 數量不均衡,或者是本身日誌產生也不平均時就會出現明顯的負債不平衡。

而且也無法針對某些日誌高峯場景進行調優(畢竟所有的 Pod 都是使用的一個日誌採集器)。

所以節點級的日誌採集更適用與該 worker 節點負債較低的時候使用,也更容易維護。

Sidecar 代理模式

第二種相對於第一種可以理解爲由集中式的日誌採集分散到各個應用 Pod 中自行採集。

需要爲每一個業務 Pod 掛載一個邊車(sidecar)進行日誌採集,由於邊車和業務 Pod 共享的是一個存儲,所以可以很方便的讀取到日誌。

由於它是和應用掛載在一起的,所以資源佔用自然會比節點採集更多,同理耦合度也增加了,採集組件的升級可能還會影響的業務 Pod。

但同樣的帶來好處就是可以針對單個 Pod 更精細的控制採集方案。

比如對於一些日誌寫入頻繁的應用,可以將 filebeat 的配置提高,甚至還可以將這種高負載的日誌單獨寫入一個 elasticsearch 中,這樣可以與普通負載的日誌進行資源隔離。

這個方案更適用與集羣規模較大的場景,需要做一些精細化配置。


我們其實也是採用的也是這個方案,不過具體細節稍有不同。

我們沒有直接使用標準輸入和輸出,原因如下:

日誌格式沒法統一,導致最終查詢的時候無法做一些標準化的限制(比如我們要求每個日誌都需要帶業務 id、traceId 等,查詢時候有這些業務指標就很容易沉澱一些標準的查詢語句。)

最終我們還是採用了 Java 的老朋友,logback 配置了自己的日誌格式,所有的應用都會根據這個模版進行日誌輸出。

同時利用日誌框架的批量寫入、緩衝等特性還更容易進行日誌的性能調優。(只使用標準輸出時對應用來說是黑盒。)

最終我們的技術方案是:

直接寫入

還有一種方案是直接寫入,這個其實和 kubernetes 本身就沒有太多關係了。

由業務自己調用 elasticsearch 或者其他的存儲組件的 API 進行寫入,這種通常適用於對性能要求較高的場景,略過了中間的採集步驟,直接寫入存儲端。

這個我在 VictoriaLogs:一款超低佔用的 ElasticSearch 替代方案中介紹過,我需要在 broker 的攔截器中埋點消息信息,從而可以生成一個消息🆔的鏈路信息。

因爲需要攔截消息的發送、消費的各個階段,加上併發壓力較高,所以對日誌的寫入性能要求還是蠻高的。

因此就需要在攔截器中直接對寫入到日誌存儲。

這裏考慮到我這裏的但一場景,以及對資源的消耗,最終選取了 victoriaLog 這個日誌存儲。

而在發送日誌的時候也得用了高性能的日誌發生框架,這裏選取了aliyun-log-java-producer然後做了一些定製。

這個庫支持以下一些功能:

  • 高性能:批量發送、多線程等
  • 自動重試
  • 異步非阻塞
  • 資源控制(可以對內存、數量進行控制)

因爲這是爲阿里雲日誌服務的一個組件,代碼裏硬編碼了只能寫入阿里的日誌服務。

所以拿來稍加改造後,現在可以支持自定義發送到任意後端,只需要在初始化時自定義實現發送回調接口即可:

ProducerConfig producerConfig = new ProducerConfig();
producerConfig.setSenderArgs(new Object[]{vlogUrl, client});
producerConfig.setSender((batch, args) -> {
    StringBuilder body = new StringBuilder();
    for (String s : batch.getLogItemsString()) {
        body.append("{\"create\":{}}");
        body.append("\n");
        body.append(s);
        body.append("\n");
    }
    RequestBody requestBody =
            RequestBody.create(MediaType.parse("application/json"), body.toString());
    Request request =
            new Request.Builder()
                    .url(String.format("%s/insert/elasticsearch/_bulk", args[0]))
                    .post(requestBody)
                    .build();

    OkHttpClient okHttpClient = (OkHttpClient) args[1];
    try (Response response = okHttpClient.newCall(request).execute()) {
        if (response.isSuccessful()) {
        } else {
            log.error("Request failed with error code: " + response);
        }
    } catch (IOException e) {
        log.error("Send vlogs failed", e);
        throw e;
    }
});
logProducer = new LogProducer(producerConfig);

考慮到這個庫只是對阿里雲日誌服務的一個組件,加上代碼已經很久沒維護了,所以就沒有將這部分代碼提交到社區,感興趣的評論區留言。

日誌安全

日誌是一個非常基礎但又很敏感的功能,首先是編碼規範上要避免打印一些敏感信息;比如身份證、密碼等;同時對日誌的訪問也要最好權限控制。

在我們內部的研效平臺中,對於日誌、監控等功能都是和應用權限掛鉤的。

簡單來說就是關閉了統一查詢 ES 的入口,只在應用層級提供查詢,類似於:

圖來自於 orbit 產品。

OpenTelemetry

當然講到日誌目前自然也逃不過 OpenTelemetry,作爲當前雲原生可觀測性的標準也提供了對應的日誌組件。

OpenTelemetry 也定義了結構化的日誌格式:

{"resourceLogs":[{"resource":{"attributes":[{"key":"resource-attr","value":{"stringValue":"resource-attr-val-1"}}]},"scopeLogs":[{"scope":{},"logRecords":[{"timeUnixNano":"1581452773000000789","severityNumber":9,"severityText":"Info""body":{"stringValue":"This is a log message"},"attributes":[{"key":"app","value":{"stringValue":"server"}},{"key":"instance_num","value":{"intValue":"1"}}],"droppedAttributesCount":1,"traceId":"08040201000000000000000000000000","spanId":"0102040800000000"},{"timeUnixNano":"1581452773000000789","severityNumber":9,"severityText":"Info","body":{"stringValue":"something happened"},"attributes":[{"key":"customer","value":{"stringValue":"acme"}},{"key":"env","value":{"stringValue":"dev"}}],"droppedAttributesCount":1,"traceId":"","spanId":""}]}]}]}

我們可以配置 otel.logs.exporter=otlp (default) 可以將日誌輸出到 oetl-collector 中,再由他輸出到後端存儲中。

雖然這樣 otel-collectoer 就成爲瓶頸了,但我們也可以部署多個副本來降低壓力。

同時也可以在應用中指定不同的 endpoint(otel.exporter.otlp.endpoint=http://127.0.0.1:4317) 來區分日誌的 collector,與其他類型的 collector 做到資源隔離。

不過目前社區關於日誌的實踐還比較少,而且由於版本 1.0 版本 release 的時間也不算長,穩定性和之前的 filebeat 相比還得需要時間檢驗。

總結

理想情況下,我們需要將可觀測性的三個重要組件都關聯起來才能更好的排查定位問題。

比如當收到監控系統通過指標變化發出的報警時,可以通過鏈路追蹤定位具體是哪個系統觸發的問題。

之後通過 traceID 定位到具體的日誌,再通過日誌的上下文列出更多日誌信息,這樣整個鏈條就可以串聯起來,可以極大的提高效率。

參考鏈接:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章