這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
目前最為主流的容器編排工具主要有kubernetes、mesos、swarm,個人不評價誰好誰壞因為每個東西都有自己的優勢。不過個人認為目前關注度最高的應該當屬kubernetes,現在越來越多的公司採用kubernetes作為底層編排工具開發自己的容器調度平台。既然是一個PAAS平台那麼就應該提供一個計算監控等一體的服務,因為是在kubernetes運行上面的容器大多數都是無狀態服務,所以統一的日誌管理又是其中必不可少的一部分。下面我們就講一下如何基於filebeat開發屬於自己的日誌採集。
目前用的最多的日誌管理技術應該是ELK,E應該沒有太多的疑問基本上很多公司都是採用的這個作為儲存索引引擎。L及logstash是一個日誌採集工具支援檔案採集等多種方式,但是基於容器的日誌採集又跟傳統的檔案采方式略有不同,雖然docker本身提供了一些log driver但是還是無法很好的滿足我們的需求。現在kubernetes官方有一個日誌解決方案是基於fluentd的。至於為什麼最後選擇採用filebeat而沒有用fluentd主要有一下幾點:
- 首先filebeat是go寫的,我本身是go開發,fluentd是ruby寫的很抱歉我看不太懂
- filbeat比較輕量,filbeat現在功能雖然比較簡單但是已經基本上夠用,而且打出來鏡像只有幾十M
- filbeat效能比較好,沒有具體跟fluentd對比過,之前跟logstash對比過確實比logstash好不少,logtash也是ruby寫的我想應該會比fluentd好不少
- filbeat雖然功能簡單,但是代碼結構非常易於進行定製開發
- 還有就是雖然用了很久fluentd但是fluentd的設定檔實在是讓我很難懂
filebeat如何採集kubernetes日誌
所以基於以上幾點決定採用filebeat開發了自己的日誌採集。
filebeat的Github地址是https://github.com/elastic/beats
裡面囊括了好幾個項目其中就包括filebeat。
和其他的日誌採集處理一樣filebeat也有幾個部分分別是input、processors、output,不過filebeat提供的能力還比較少,不過無所謂夠用就好。
filebeat提供了一個add_kubernetes_metadata
的processor,檔案的採集路徑就要配成/var/lib/docker/containers/*/*-json.log
主要是監聽kubernetes的apiserver把容器對應的pod的資訊存到記憶體裡面,從檔案日誌source裡面(就是上面的那個路徑)裡面擷取容器id匹配得到pod的資訊。
因為json.log檔案裡面的日誌都是json格式的所以需要對日誌進行json格式化,filebeat有一個processor叫decode_json_fields
這些processor都支援條件判斷,可以通過條件判斷來絕對是否要對某一條日誌進行處理。filebeat預設的日誌欄位是message但是*-json.log解析出來以後的日誌欄位是log,如果同時配置了其他的日誌採集這個時候所用的儲存日誌的欄位就不一樣了,所以需要對它們進行處理讓它們使用同一個欄位,但是filebeat並沒有提供這個功能所以自己寫了一個add_fields
的功能。
整理後的設定檔如下:
filebeat.prospectors:- type: log paths: - /var/lib/docker/containers/*/*-json.log - /var/log/containers/applogs/*processors:- add_kubernetes_metadata: in_cluster: false host: "127.0.0.1" kube_config: /root/.kube/config- add_fields: fields: log: '{message}'- decode_json_fields: when: regexp: log: "{*}" fields: ["log"] overwrite_keys: true target: ""- drop_fields: fields: ["source", "beat.version", "beat.name", "message"]- parse_level: levels: ["fatal", "error", "warn", "info", "debug"] field: "log"logging.level: infosetup.template.enabled: truesetup.template.name: "filebeat-%{+yyyy.MM.dd}"setup.template.pattern: "filebeat-*"#setup.template.fields: "${path.config}/fields.yml"setup.template.fields: "/fields.yml"setup.template.overwrite: truesetup.template.settings: index: analysis: analyzer: enncloud_analyzer: filter: ["standard", "lowercase", "stop"] char_filter: ["my_filter"] type: custom tokenizer: standard char_filter: my_filter: type: mapping mappings: ["-=>_"]output: elasticsearch: hosts: ["127.0.0.1:9200"] index: "filebeat-%{+yyyy.MM.dd}"
如果線上環境filebeat也是以daemonset的方式運行在kubernetes叢集裡面,所以in_cluster
就需要設定成true,對應的kube_config
則不需要配置了,host參數則是監聽的某一個節點的pod,所以這個值應該是filebeat運行所在節點的pod的名稱,當然也可以不寫,那樣的話就是監聽全域的pod,不過這個對於filebeat來說是沒必要的也是不好的。
add_fields
processor可以添加自己想要的欄位,值可以是字串也可以是{message}
格式,如果是這種格式則會從已有的欄位裡面取值進行填充。
parse_level
processor是用於一個匹配日誌格式的功能,如果記錄檔最前面出現的那個記錄層級則這個日誌加一個相應層級的欄位。
filebeat還有對於template處理的功能的功能可以指定所用的mapping。
開發filebeat processor
使用的過程中主要是針對一些不滿足的processor進行了開發,filebeat的代碼結構非常清晰抽象也很好,可以很簡單的進行開發。
filebeat的processor功能主要放在libbeat和filbeat同級的目錄下,在這個目錄下就叫processors。可以看到裡面有actions
,add_cloud_metadata
、add_kubernetes_metadata
、add_docker_metadata
所以filebeat也只支援直接docker的processor的,比較普通的processor都是放在actions下面的所以如果我們需要開發一些簡單的processor的話可以直接放到下面,包括decode_json和drop_event等也是放在下面的。以add_field
為例:
package actionsimport ( "fmt" "regexp" "strings" "github.com/elastic/beats/libbeat/beat" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/processors")type addFields struct { Fields map[string]string reg *regexp.Regexp}func init() { processors.RegisterPlugin("add_fields", configChecked(newAddFields, requireFields("fields"), allowedFields("fields", "when")))}func newAddFields(c *common.Config) (processors.Processor, error) { config := struct { Fields map[string]string `config:"fields"` }{} err := c.Unpack(&config) if err != nil { return nil, fmt.Errorf("fail to unpack the add_fields configuration: %s", err) } f := &addFields{Fields: config.Fields, reg: regexp.MustCompile("{(.*)}")} return f, nil}func (f *addFields) Run(event *beat.Event) (*beat.Event, error) { var errors []string for field, value := range f.Fields { matchers := f.reg.FindAllStringSubmatch(value, -1) if len(matchers) == 0 { event.PutValue(field, value) } else { if len(matchers[0]) >= 2 { val, err := event.GetValue(strings.Trim(matchers[0][1], " ")) if err != nil { errors = append(errors, err.Error()) } else { event.PutValue(field, val) } } } } return event, nil}func (f *addFields) String() string { var fields []string for field, _ := range f.Fields { fields = append(fields, field) } return "add_fields=" + strings.Join(fields, ", ")}
需要定義自己的struct, newAddFields方法通過設定檔初始化自己的struct。並在init裡面通過RegisterPlugin把自己的processor註冊進去。這個struct主要是要實現Run方法,這個方法就是對於每一條日誌event的具體處理。
到這就基本上實現了對接kubernetes的對接改造就基本上完成了,當然還有其他很多工作可以做,比如golang本身的regex和encoding/json效能比較差,這些都是可以最佳化的地方。
我自己fork出來的地址是https://github.com/yiqinguo/beats
增加了Makefile直接編譯打鏡像,和filebeat-ds.yml直接發到kubernetes叢集裡面。