Kubernetes集群的日志EFK解決方案


k8s日志收集架構

https://kubernetes.io/docs/concepts/cluster-administration/logging/

總體分為三種方式:

  • 使用在每個節點上運行的節點級日志記錄代理。
  • 在應用程序的 pod 中,包含專門記錄日志的 sidecar 容器。
  • 將日志直接從應用程序中推送到日志記錄后端。
使用節點級日志代理

image
容器日志驅動:

https://docs.docker.com/config/containers/logging/configure/

查看當前的docker主機的驅動:

$ docker info --format '{{.LoggingDriver}}'

json-file格式,docker會默認將標准和錯誤輸出保存為宿主機的文件,路徑為:

/var/lib/docker/containers/<container-id>/<container-id>-json.log

並且可以設置日志輪轉:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3",
    "labels": "production_status",
    "env": "os,customer"
  }
}

優勢:

  • 部署方便,使用DaemonSet類型控制器來部署agent即可
  • 對業務應用的影響最小,沒有侵入性

劣勢:

  • 只能收集標准和錯誤輸出,對於容器內的文件日志,暫時收集不到
使用 sidecar 容器和日志代理
  • 方式一:sidecar 容器將應用程序日志傳送到自己的標准輸出。

    思路:在pod中啟動一個sidecar容器,把容器內的日志文件吐到標准輸出,由宿主機中的日志收集agent進行采集。
    image

    $ cat count-pod.yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: counter
    spec:
      containers:
      - name: count
        image: busybox
        args:
        - /bin/sh
        - -c
        - >
          i=0;
          while true;
          do
            echo "$i: $(date)" >> /var/log/1.log;
            echo "$(date) INFO $i" >> /var/log/2.log;
            i=$((i+1));
            sleep 1;
          done
        volumeMounts:
        - name: varlog
          mountPath: /var/log
      - name: count-log-1
        image: busybox
        args: [/bin/sh, -c, 'tail -n+1 -f /var/log/1.log']
        volumeMounts:
        - name: varlog
          mountPath: /var/log
      - name: count-log-2
        image: busybox
        args: [/bin/sh, -c, 'tail -n+1 -f /var/log/2.log']
        volumeMounts:
        - name: varlog
          mountPath: /var/log
      volumes:
      - name: varlog
        emptyDir: {}
        
    $ kubectl create -f counter-pod.yaml
    $ kubectl logs -f counter -c count-log-1
    

    優勢:

    • 可以實現容器內部日志收集
    • 對業務應用的侵入性不大

    劣勢:

    • 每個業務pod都需要做一次改造
    • 增加了一次日志的寫入,對磁盤使用率有一定影響
  • 方式二:sidecar 容器運行一個日志代理,配置該日志代理以便從應用容器收集日志。

image
思路:直接在業務Pod中使用sidecar的方式啟動一個日志收集的組件(比如fluentd),這樣日志收集可以將容器內的日志當成本地文件來進行收取。

優勢:不用往宿主機存儲日志,本地日志完全可以收集

劣勢:每個業務應用額外啟動一個日志agent,帶來額外的資源損耗

從應用中直接暴露日志目錄

image

企業日志方案選型

目前來講,最建議的是采用節點級的日志代理。

方案一:自研方案,實現一個自研的日志收集agent,大致思路:

  • 針對容器的標准輸出及錯誤輸出,使用常規的方式,監聽宿主機中的容器輸出路徑即可
  • 針對容器內部的日志文件
    • 在容器內配置統一的環境變量,比如LOG_COLLECT_FILES,指定好容器內待收集的日志目錄及文件
    • agent啟動的時候掛載docker.sock文件及磁盤的根路徑
    • 監聽docker的容器新建、刪除事件,通過docker的api,查出容器的存儲、環境變量、k8s屬性等信息
    • 配置了LOG_COLLECT_FILES環境變量的容器,根據env中的日志路徑找到主機中對應的文件路徑,然后生成收集的配置文件
    • agent與開源日志收集工具(Fluentd或者filebeat等)配合,agent負責下發配置到收集工具中並對進程做reload

方案二:日志使用開源的Agent進行收集(EFK方案),適用范圍廣,可以滿足絕大多數日志收集、展示的需求。

使用EFK實現業務日志收集

EFK架構工作流程

image

  • Elasticsearch

    一個開源的分布式、Restful 風格的搜索和數據分析引擎,它的底層是開源庫Apache Lucene。它可以被下面這樣准確地形容:

    • 一個分布式的實時文檔存儲,每個字段可以被索引與搜索;
    • 一個分布式實時分析搜索引擎;
    • 能勝任上百個服務節點的擴展,並支持 PB 級別的結構化或者非結構化數據。
  • Kibana

    Kibana是一個開源的分析和可視化平台,設計用於和Elasticsearch一起工作。可以通過Kibana來搜索,查看,並和存儲在Elasticsearch索引中的數據進行交互。也可以輕松地執行高級數據分析,並且以各種圖標、表格和地圖的形式可視化數據。

  • Fluentd

    一個針對日志的收集、處理、轉發系統。通過豐富的插件系統,可以收集來自於各種系統或應用的日志,轉化為用戶指定的格式后,轉發到用戶所指定的日志存儲系統之中。
    image
    Fluentd 通過一組給定的數據源抓取日志數據,處理后(轉換成結構化的數據格式)將它們轉發給其他服務,比如 Elasticsearch、對象存儲、kafka等等。Fluentd 支持超過300個日志存儲和分析服務,所以在這方面是非常靈活的。主要運行步驟如下

    1. 首先 Fluentd 從多個日志源獲取數據
    2. 結構化並且標記這些數據
    3. 然后根據匹配的標簽將數據發送到多個目標服務

Fluentd架構

image
為什么推薦使用fluentd作為k8s體系的日志收集工具?

fluentd事件流的生命周期及指令配置

https://docs.fluentd.org/v/0.12/quickstart/life-of-a-fluentd-event

Input -> filter 1 -> ... -> filter N -> Buffer -> Output

指令介紹:

  • source ,數據源,對應Input
    通過使用 source 指令,來選擇和配置所需的輸入插件來啟用 Fluentd 輸入源, source 把事件提交到 fluentd 的路由引擎中。使用type來區分不同類型的數據源。如下配置可以監聽指定文件的追加輸入:

    <source>
      @type tail
      path /var/log/httpd-access.log
      pos_file /var/log/td-agent/httpd-access.log.pos
      tag myapp.access
      format apache2
    </source>
    
    
  • filter,Event processing pipeline(事件處理流)

    filter 可以串聯成 pipeline,對數據進行串行處理,最終再交給 match 輸出。 如下可以對事件內容進行處理:

    <source>
      @type http
      port 9880
    </source>
    
    <filter myapp.access>
      @type record_transformer
      <record>
        host_param “#{Socket.gethostname}”
      </record>
    </filter>
    

    filter 獲取數據后,調用內置的 @type record_transformer 插件,在事件的 record 里插入了新的字段 host_param,然后再交給 match 輸出。

  • label指令

    可以在 source 里指定 @label,這個 source 所觸發的事件就會被發送給指定的 label 所包含的任務,而不會被后續的其他任務獲取到。

    <source>
      @type forward
    </source>
    
    <source>
    ### 這個任務指定了 label 為 @SYSTEM
    ### 會被發送給 <label @SYSTEM>
    ### 而不會被發送給下面緊跟的 filter 和 match
      @type tail
      @label @SYSTEM
      path /var/log/httpd-access.log
      pos_file /var/log/td-agent/httpd-access.log.pos
      tag myapp.access
      format apache2
    </source>
    
    <filter access.**>
      @type record_transformer
      <record>
      # …
      </record>
    </filter>
    
    <match **>
      @type elasticsearch
      # …
    </match>
    
    <label @SYSTEM>
      ### 將會接收到上面 @type tail 的 source event
      <filter var.log.middleware.**>
        @type grep
        # …
      </filter>
    
      <match **>
        @type s3
        # …
      </match>
    </label>
    
  • match,匹配輸出

    查找匹配 “tags” 的事件,並處理它們。match 命令的最常見用法是將事件輸出到其他系統(因此,與 match 命令對應的插件稱為 “輸出插件”)

    <source>
      @type http
      port 9880
    </source>
    
    <filter myapp.access>
      @type record_transformer
      <record>
        host_param “#{Socket.gethostname}”
      </record>
    </filter>
    
    <match myapp.access>
      @type file
      path /var/log/fluent/access
    </match>
    

事件的結構:

time:事件的處理時間

tag:事件的來源,在fluentd.conf中配置

record:真實的日志內容,json對象

比如,下面這條原始日志:

192.168.0.1 - - [28/Feb/2013:12:00:00 +0900] "GET / HTTP/1.1" 200 777

經過fluentd 引擎處理完后的樣子可能是:

2020-07-16 08:40:35 +0000 apache.access: {"user":"-","method":"GET","code":200,"size":777,"host":"192.168.0.1","path":"/"}
fluentd的buffer事件緩沖模型
Input -> filter 1 -> ... -> filter N -> Buffer -> Output

image
因為每個事件數據量通常很小,考慮數據傳輸效率、穩定性等方面的原因,所以基本不會每條事件處理完后都會立馬寫入到output端,因此fluentd建立了緩沖模型,模型中主要有兩個概念:

  • buffer_chunk:事件緩沖塊,用來存儲本地已經處理完待發送至目的端的事件,可以設置每個塊的大小。
  • buffer_queue:存儲chunk的隊列,可以設置長度

可以設置的參數,主要有:

  • buffer_type,緩沖類型,可以設置file或者memory
  • buffer_chunk_limit,每個chunk塊的大小,默認8MB
  • buffer_queue_limit ,chunk塊隊列的最大長度,默認256
  • flush_interval ,flush一個chunk的時間間隔
  • retry_limit ,chunk塊發送失敗重試次數,默認17次,之后就丟棄該chunk數據
  • retry_wait ,重試發送chunk數據的時間間隔,默認1s,第2次失敗再發送的話,間隔2s,下次4秒,以此類推

大致的過程為:

隨着fluentd事件的不斷生成並寫入chunk,緩存塊持變大,當緩存塊滿足buffer_chunk_limit大小或者新的緩存塊誕生超過flush_interval時間間隔后,會推入緩存queue隊列尾部,該隊列大小由buffer_queue_limit決定。

每次有新的chunk入列,位於隊列最前部的chunk塊會立即寫入配置的存儲后端,比如配置的是kafka,則立即把數據推入kafka中。

比較理想的情況是每次有新的緩存塊進入緩存隊列,則立馬會被寫入到后端,同時,新緩存塊也持續入列,但是入列的速度不會快於出列的速度,這樣基本上緩存隊列處於空的狀態,隊列中最多只有一個緩存塊。

但是實際情況考慮網絡等因素,往往緩存塊被寫入后端存儲的時候會出現延遲或者寫入失敗的情況,當緩存塊寫入后端失敗時,該緩存塊還會留在隊列中,等retry_wait時間后重試發送,當retry的次數達到retry_limit后,該緩存塊被銷毀(數據被丟棄)。

此時緩存隊列持續有新的緩存塊進來,如果隊列中存在很多未及時寫入到后端存儲的緩存塊的話,當隊列長度達到buffer_queue_limit大小,則新的事件被拒絕,fluentd報錯,error_class=Fluent::Plugin::Buffer::BufferOverflowError error="buffer space has too many data"。

還有一種情況是網絡傳輸緩慢的情況,若每3秒鍾會產生一個新塊,但是寫入到后端時間卻達到了30s鍾,隊列長度為100,那么每個塊出列的時間內,又有新的10個塊進來,那么隊列很快就會被占滿,導致異常出現。

實踐一:實現業務應用日志的收集及字段解析

目標:收集容器內的nginx應用的access.log日志,並解析日志字段為JSON格式,原始日志的格式為:

$ tail -f access.log
...
53.49.146.149 1561620585.973 0.005 502 [27/Jun/2019:15:29:45 +0800] 178.73.215.171 33337 GET https

收集並處理成:

{
    "serverIp": "53.49.146.149",
    "timestamp": "1561620585.973",
    "respondTime": "0.005",
    "httpCode": "502",
    "eventTime": "27/Jun/2019:15:29:45 +0800",
    "clientIp": "178.73.215.171",
    "clientPort": "33337",
    "method": "GET",
    "protocol": "https"
}

思路:

  • 配置fluent.conf
    • 使用@tail插件通過監聽access.log文件
    • 用filter實現對nginx日志格式解析
  • 啟動fluentd服務
  • 手動追加內容至access.log文件
  • 觀察本地輸出內容是否符合預期

fluent.conf

<source>
	@type tail
	@label @nginx_access
	path /fluentd/access.log
	pos_file /fluentd/nginx_access.posg
	tag nginx_access
	format none
	@log_level trace
</source>
<label @nginx_access>
   <filter  nginx_access>
       @type parser
	   key_name message
	   format  /(?<serverIp>[^ ]*) (?<timestamp>[^ ]*) (?<respondTime>[^ ]*) (?<httpCode>[^ ]*) \[(?<eventTime>[^\]]*)\] (?<clientIp>[^ ]*) (?<clientPort>[^ ]*) (?<method>[^ ]*) (?<protocol>[^ ]*)/
   </filter>
   <match  nginx_access>
     @type stdout
   </match>
</label>

啟動服務,追加文件內容:

$ docker run -u root --rm -ti 192.168.136.10:5000/fluentd_elasticsearch/fluentd:v2.5.2 sh
/ # cd /fluentd/
/ # touch access.log
/ # fluentd -c /fluentd/etc/fluent.conf
/ # echo '53.49.146.149 1561620585.973 0.005 502 [27/Jun/2019:15:29:45 +0800] 178.73.215.171 33337 GET https' >>/fluentd/access.log

使用該網站進行正則校驗: http://fluentular.herokuapp.com

實踐二:使用ruby實現日志字段的轉換及自定義處理
<source>
	@type tail
	@label @nginx_access
	path /fluentd/access.log
	pos_file /fluentd/nginx_access.posg
	tag nginx_access
	format none
	@log_level trace
</source>
<label @nginx_access>
   <filter  nginx_access>
       @type parser
	   key_name message
	   format  /(?<serverIp>[^ ]*) (?<timestamp>[^ ]*) (?<respondTime>[^ ]*) (?<httpCode>[^ ]*) \[(?<eventTime>[^\]]*)\] (?<clientIp>[^ ]*) (?<clientPort>[^ ]*) (?<method>[^ ]*) (?<protocol>[^ ]*)/
   </filter>
   <filter  nginx_access>   
	   @type record_transformer
	   enable_ruby
       <record>
		host_name "#{Socket.gethostname}"
        my_key  "my_val"
        tls ${record["protocol"].index("https") ? "true" : "false"}
       </record>
   </filter>
   <match  nginx_access>
     @type stdout
   </match>
</label>
部署es服務
部署分析
  1. es生產環境是部署es集群,通常會使用statefulset進行部署,此例由於演示環境資源問題,部署為單點
  2. 數據存儲掛載主機路徑
  3. es默認使用elasticsearch用戶啟動進程,es的數據目錄是通過宿主機的路徑掛載,因此目錄權限被主機的目錄權限覆蓋,因此可以利用initContainer容器在es進程啟動之前把目錄的權限修改掉,注意init container要用特權模式啟動。
  4. 若希望使用helm部署,參考 https://github.com/helm/charts/tree/master/stable/elasticsearch
部署並驗證

efk/elasticsearch.yaml

apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
    k8s-app: elasticsearch-logging
    version: v7.4.2
  name: elasticsearch-logging
  namespace: logging
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: elasticsearch-logging
      version: v7.4.2
  serviceName: elasticsearch-logging
  template:
    metadata:
      labels:
        k8s-app: elasticsearch-logging
        version: v7.4.2
    spec:
      nodeSelector:
        es: "true"	## 指定部署在哪個節點。需根據環境來修改
      containers:
      - env:
        - name: NAMESPACE
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
        - name: cluster.initial_master_nodes
          value: elasticsearch-logging-0
        - name: ES_JAVA_OPTS
          value: "-Xms512m -Xmx512m"
        image: 192.168.136.10:5000/elasticsearch/elasticsearch:7.4.2
        name: elasticsearch-logging
        ports:
        - containerPort: 9200
          name: db
          protocol: TCP
        - containerPort: 9300
          name: transport
          protocol: TCP
        volumeMounts:
        - mountPath: /usr/share/elasticsearch/data
          name: elasticsearch-logging
      dnsConfig:
        options:
        - name: single-request-reopen
      initContainers:
      - command:
        - /sbin/sysctl
        - -w
        - vm.max_map_count=262144
        image: alpine:3.6
        imagePullPolicy: IfNotPresent
        name: elasticsearch-logging-init
        resources: {}
        securityContext:
          privileged: true
      - name: fix-permissions
        image: alpine:3.6
        command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
        securityContext:
          privileged: true
        volumeMounts:
        - name: elasticsearch-logging
          mountPath: /usr/share/elasticsearch/data
      volumes:
      - name: elasticsearch-logging
        hostPath:
          path: /esdata
---
apiVersion: v1
kind: Service
metadata:
  labels:
    k8s-app: elasticsearch-logging
  name: elasticsearch
  namespace: logging
spec:
  ports:
  - port: 9200
    protocol: TCP
    targetPort: db
  selector:
    k8s-app: elasticsearch-logging
  type: ClusterIP
$ kubectl create namespace logging
## 給slave1節點打上label,將es服務調度到slave1節點
$ kubectl label node k8s-slave1 es=true
## 部署服務,可以先去部署es的節點把鏡像下載到本地
$ kubectl create -f elasticsearch.yaml
statefulset.apps/elasticsearch-logging created  
service/elasticsearch created

## 等待片刻,查看一下es的pod部署到了k8s-slave1節點,狀態變為running
$ kubectl -n logging get po -o wide  
NAME                      READY   STATUS    RESTARTS   AGE   IP             NODE       
elasticsearch-logging-0   1/1     Running   0          69m   10.244.1.104   k8s-slave1   
# 然后通過curl命令訪問一下服務,驗證es是否部署成功
$ kubectl -n logging get svc  
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE  
elasticsearch   ClusterIP   10.109.174.58   <none>        9200/TCP   71m  
$ curl 10.109.174.58:9200  
{  
  "name" : "elasticsearch-logging-0",  
  "cluster_name" : "docker-cluster",  
  "cluster_uuid" : "uic8xOyNSlGwvoY9DIBT1g",  
  "version" : {  
	"number" : "7.4.2",  
	"build_flavor" : "default",  
	"build_type" : "docker",  
	"build_hash" : "2f90bbf7b93631e52bafb59b3b049cb44ec25e96",  
	"build_date" : "2019-10-28T20:40:44.881551Z",  
	"build_snapshot" : false,  
	"lucene_version" : "8.2.0",  
	"minimum_wire_compatibility_version" : "6.8.0",  
	"minimum_index_compatibility_version" : "6.0.0-beta1"  
  },  
  "tagline" : "You Know, for Search"  
}  
部署kibana
部署分析
  1. kibana需要暴露web頁面給前端使用,因此使用ingress配置域名來實現對kibana的訪問

  2. kibana為無狀態應用,直接使用Deployment來啟動

  3. kibana需要訪問es,直接利用k8s服務發現訪問此地址即可,http://elasticsearch:9200

部署並驗證

efk/kibana.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: logging
  labels:
    app: kibana
spec:
  selector:
    matchLabels:
      app: "kibana"
  template:
    metadata:
      labels:
        app: kibana
    spec:
      nodeSelector:
        kibana: "true"	## 指定部署在哪個節點。需根據環境來修改
      containers:
      - name: kibana
        image: 192.168.136.10:5000/kibana/kibana:7.4.2
        resources:
          limits:
            cpu: 1000m
          requests:
            cpu: 100m
        env:
          - name: ELASTICSEARCH_URL
            value: http://elasticsearch:9200
        ports:
        - containerPort: 5601
---
apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: logging
  labels:
    app: kibana
spec:
  ports:
  - port: 5601
    protocol: TCP
    targetPort: 5601
  type: ClusterIP
  selector:
    app: kibana
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: kibana
  namespace: logging
spec:
  rules:
  - host: kibana.luffy.com
    http:
      paths:
      - path: /
        backend:
          serviceName: kibana
          servicePort: 5601
$ kubectl label node k8s-slave2 kibana=true
$ kubectl create -f kibana.yaml  
deployment.apps/kibana created
service/kibana created  
ingress/kibana created

# 然后查看pod,等待狀態變成running
$ kubectl -n logging get po  
NAME                      READY   STATUS    RESTARTS   AGE  
elasticsearch-logging-0   1/1     Running   0          88m  
kibana-944c57766-ftlcw    1/1     Running   0          15m

## 配置域名解析 kibana.luffy.com,並訪問服務進行驗證,若可以訪問,說明連接es成功
Fluentd服務部署
部署分析
  1. fluentd為日志采集服務,kubernetes集群的每個業務節點都有日志產生,因此需要使用daemonset的模式進行部署
  2. 為進一步控制資源,會為daemonset指定一個選擇標簽,fluentd=true來做進一步過濾,只有帶有此標簽的節點才會部署fluentd
  3. 日志采集,需要采集哪些目錄下的日志,采集后發送到es端,因此需要配置的內容比較多,我們選擇使用configmap的方式把配置文件整個掛載出來
部署服務

efk/fluentd-es-config-main.yaml

apiVersion: v1
data:
  fluent.conf: |-
    # This is the root config file, which only includes components of the actual configuration
    #
    #  Do not collect fluentd's own logs to avoid infinite loops.
    <match fluent.**>
    @type null
    </match>

    @include /fluentd/etc/config.d/*.conf
kind: ConfigMap
metadata:
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
  name: fluentd-es-config-main
  namespace: logging

配置文件,fluentd-config.yaml,注意點:

  1. 數據源source的配置,k8s會默認把容器的標准和錯誤輸出日志重定向到宿主機中
  2. 默認集成了 kubernetes_metadata_filter 插件,來解析日志格式,得到k8s相關的元數據,raw.kubernetes
  3. match輸出到es端的flush配置

efk/fluentd-configmap.yaml

kind: ConfigMap
apiVersion: v1
metadata:
  name: fluentd-config
  namespace: logging
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
data:
  containers.input.conf: |-
    <source>
      @id fluentd-containers.log
      @type tail
      path /var/log/containers/*.log
      pos_file /var/log/es-containers.log.pos
      time_format %Y-%m-%dT%H:%M:%S.%NZ
      localtime
      tag raw.kubernetes.*
      format json
      read_from_head true
    </source>
    # Detect exceptions in the log output and forward them as one log entry.
    # https://github.com/GoogleCloudPlatform/fluent-plugin-detect-exceptions 
    <match raw.kubernetes.**>
      @id raw.kubernetes
      @type detect_exceptions
      remove_tag_prefix raw
      message log
      stream stream
      multiline_flush_interval 5
      max_bytes 500000
      max_lines 1000
    </match>
  output.conf: |-
    # Enriches records with Kubernetes metadata
    <filter kubernetes.**>
      @type kubernetes_metadata
    </filter>
    <match **>
      @id elasticsearch
      @type elasticsearch
      @log_level info
      include_tag_key true
      host elasticsearch
      port 9200
      logstash_format true
      request_timeout    30s
      <buffer>
        @type file
        path /var/log/fluentd-buffers/kubernetes.system.buffer
        flush_mode interval
        retry_type exponential_backoff
        flush_thread_count 2
        flush_interval 5s
        retry_forever
        retry_max_interval 30
        chunk_limit_size 2M
        queue_limit_length 8
        overflow_action block
      </buffer>
    </match>

daemonset定義文件,fluentd.yaml,注意點:

  1. 需要配置rbac規則,因為需要訪問k8s api去根據日志查詢元數據
  2. 需要將/var/log/containers/目錄掛載到容器中
  3. 需要將fluentd的configmap中的配置文件掛載到容器內
  4. 想要部署fluentd的節點,需要添加fluentd=true的標簽

efk/fluentd.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd-es
  namespace: logging
  labels:
    k8s-app: fluentd-es
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: fluentd-es
  labels:
    k8s-app: fluentd-es
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
rules:
- apiGroups:
  - ""
  resources:
  - "namespaces"
  - "pods"
  verbs:
  - "get"
  - "watch"
  - "list"
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: fluentd-es
  labels:
    k8s-app: fluentd-es
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
subjects:
- kind: ServiceAccount
  name: fluentd-es
  namespace: logging
  apiGroup: ""
roleRef:
  kind: ClusterRole
  name: fluentd-es
  apiGroup: ""
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
    k8s-app: fluentd-es
  name: fluentd-es
  namespace: logging
spec:
  selector:
    matchLabels:
      k8s-app: fluentd-es
  template:
    metadata:
      labels:
        k8s-app: fluentd-es
    spec:
      containers:
      - env:
        - name: FLUENTD_ARGS
          value: --no-supervisor -q
        image: 192.168.136.10:5000/fluentd_elasticsearch/fluentd:v2.5.2
        imagePullPolicy: IfNotPresent
        name: fluentd-es
        resources:
          limits:
            memory: 500Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - mountPath: /var/log
          name: varlog
        - mountPath: /var/lib/docker/containers
          name: varlibdockercontainers
          readOnly: true
        - mountPath: /fluentd/etc/config.d
          name: config-volume
        - mountPath: /fluentd/etc/fluent.conf
          name: config-volume-main
          subPath: fluent.conf
      nodeSelector:
        fluentd: "true"
      securityContext: {}
      serviceAccount: fluentd-es
      serviceAccountName: fluentd-es
      volumes:
      - hostPath:
          path: /var/log
          type: ""
        name: varlog
      - hostPath:
          path: /var/lib/docker/containers
          type: ""
        name: varlibdockercontainers
      - configMap:
          defaultMode: 420
          name: fluentd-config
        name: config-volume
      - configMap:
          defaultMode: 420
          items:
          - key: fluent.conf
            path: fluent.conf
          name: fluentd-es-config-main
        name: config-volume-main
## 給slave1打上標簽,進行部署fluentd日志采集服務
$ kubectl label node k8s-slave1 fluentd=true  
$ kubectl label node k8s-slave2 fluentd=true

# 創建服務
$ kubectl create -f fluentd-es-config-main.yaml  
configmap/fluentd-es-config-main created  
$ kubectl create -f fluentd-configmap.yaml  
configmap/fluentd-config created  
$ kubectl create -f fluentd.yaml  
serviceaccount/fluentd-es created  
clusterrole.rbac.authorization.k8s.io/fluentd-es created  
clusterrolebinding.rbac.authorization.k8s.io/fluentd-es created  
daemonset.extensions/fluentd-es created 

## 然后查看一下pod是否已經在k8s-slave1
$ kubectl -n logging get po -o wide
NAME                      READY   STATUS    RESTARTS   AGE  
elasticsearch-logging-0   1/1     Running   0          123m  
fluentd-es-246pl   		  1/1     Running   0          2m2s  
kibana-944c57766-ftlcw    1/1     Running   0          50m

上述是簡化版的k8s日志部署收集的配置,完全版的可以提供 https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/fluentd-elasticsearch 來查看。

EFK功能驗證
驗證思路

在slave節點中啟動服務,同時往標准輸出中打印測試日志,到kibana中查看是否可以收集

創建測試容器

efk/test-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  nodeSelector:
    fluentd: "true"
  containers:
  - name: count
    image: alpine:3.6
    args: [/bin/sh, -c,
            'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']
$ kubectl get po  
NAME                          READY   STATUS    RESTARTS   AGE  
counter                       1/1     Running   0          6s

配置kibana

登錄kibana界面,按照截圖的順序操作:
image
image
imageimage
可以通過其他元數據來過濾日志數據,比如可以單擊任何日志條目以查看其他元數據,如容器名稱,Kubernetes 節點,命名空間等,比如kubernetes.pod_name : counter

到這里,我們就在 Kubernetes 集群上成功部署了 EFK ,要了解如何使用 Kibana 進行日志數據分析,可以參考 Kibana 用戶指南文檔:https://www.elastic.co/guide/en/kibana/current/index.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM