從零開始搭建Kubernetes集群(七、如何監控K8S集群日志)

一、前言

上一文《從零開始搭建Kubernetes集群(六、在K8S上部署Redis 集群)》主要介紹了如何在K8S上部署一套基于StatefulSet的Redis集群。本篇將介紹一下如何在K8S上進行日志的監控。

二、架構選擇(ELK VS EFK)

ELK

我們首先介紹一下傳統的日志監控方案。其中,ELK Stack 是我們最熟悉不過的架構。所謂ELK,分別指Elastic公司的Elasticsearch、Logstash、Kibana。在比較舊的ELK架構中,Logstash身兼日志的采集、過濾兩職。但由于Logstash基于JVM,性能有一定限制,因此,目前業界更推薦使用Go語言開發FIiebeat代替Logstash的采集功能,Logstash只作為了日志過濾的中間件。

最常見的ELK架構如下:

image.png

如上圖所示,各角色功能如下:

  • 多個Filebeat在各個業務端進行日志采集,然后上傳至Logstash
  • 多個Logstash節點并行(負載均衡,不作為集群),對日志記錄進行過濾處理,然后上傳至Elasticsearch集群
  • 多個Elasticsearch構成集群服務,提供日志的索引和存儲能力
  • Kibana負責對Elasticsearch中的日志數據進行檢索、分析

當然,在該架構中,根據業務特點,還可以加入某些中間件,如Redis、Kafak等:


image.png

如上圖所示,Kafka集群作為消息緩沖隊列,可以降低大量FIlebeat對Logstash的并發訪問壓力。

EFK

目前,在K8S的日志監控解決方案中,EFK也是較常用的架構。所謂的EFK,即Elasticsearch + Fluentd + Kibana。在該架構中,Fluentd作為日志采集客戶端。但我個人認為,相對于Filebeat,Fluentd并沒有突出的優勢。并且,由于同屬于Elastic公司,Filebeat可以更好的兼容其產品棧。因此,在K8S上,我仍然推薦ELK架構。

三、日志采集方式

確定使用ELK+Filebeat作為架構后,我們還需要明確Filebeat采集K8S集群日志的方式,這也是本文的重點。官方文檔中提到了三種采集方式,這里簡單介紹一下:

方式1:Node級日志代理

在每個節點(即宿主機)上可以獨立運行一個Node級日志代理,通常的實現方式為DaemonSet。用戶應用只需要將日志寫到標準輸出,Docker 的日志驅動會將每個容器的標準輸出收集并寫入到主機文件系統,這樣Node級日志代理就可以將日志統一收集并上傳。另外,可以使用K8S的logrotate或Docker 的log-opt 選項負責日志的輪轉。

image.png

Docker默認的日志驅動(LogDriver)是json-driver,其會將日志以JSON文件的方式存儲。所有容器輸出到控制臺的日志,都會以*-json.log的命名方式保存在/var/lib/docker/containers/目錄下。對于Docker日志驅動的具體介紹,請參考官方文檔。另外,除了收集Docker容器日志,一般建議同時收集K8S自身的日志以及宿主機的所有系統日志,其位置都在var/log下。

所以,簡單來說,本方式就是在每個node上各運行一個日志代理容器,對本節點/var/log/var/lib/docker/containers/兩個目錄下的日志進行采集,然后匯總到elasticsearch集群,最后通過kibana展示。

方式2:伴生容器(sidecar container)作為日志代理

創建一個伴生容器(也可稱作日志容器),與應用程序容器在處于同一個Pod中。同時伴生容器內部運行一個獨立的、專門為收集應用日志的代理,常見的有Logstash、Fluentd 、Filebeat等。日志容器通過共享卷可以獲得應用容器的日志,然后進行上傳。

image.png

方式3:應用直接上傳日志

應用程序容器直接通過網絡連接上傳日志到后端,這是最簡單的方式。


image.png

對比

image.png

其中,相對來說,方式1在業界使用更為廣泛,并且官方也更為推薦。因此,最終我們采用ELK+Filebeat架構,并基于方式1,如下:


image.png

四、準備操作

DaemonSet概念介紹

在搭建前,我們先簡單介紹一下方式1中提到的DaemonSet,這也是一個重要的概念:

DaemonSet能夠讓所有(或者一些特定)的Node節點運行同一個pod。當節點加入到kubernetes集群中,pod會被(DaemonSet)調度到該節點上運行,當節點從kubernetes集群中被移除,被(DaemonSet)調度的pod會被移除,如果刪除DaemonSet,所有跟這個DaemonSet相關的pods都會被刪除。

因此,我們可以使用DaemonSet來部署Filebeat。這樣,每當集群加入一個新的節點,該節點就會自動創建一個Filebeat守護進程,并有且只有一個。

另外,由于篇幅限制,本文只介紹如何通過基于DaemonSet的Filebeat來收集K8S集群的日志,而非介紹如何在K8S上搭建一個ELK集群。同時,日志記錄將直接上傳至Elasticsearch中,而不通過Logstash,并且本文假設Elasticsearch集群已提前搭建完畢可直接使用。

清楚了本文的側重點后,好,走你~

官方Filebeat部署腳本介紹

這里,我們將基于Elastic官方提供的Filebeat部署腳本進行部署,如下所示:

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-config
  namespace: kube-system
  labels:
    k8s-app: filebeat
    kubernetes.io/cluster-service: "true"
data:
  filebeat.yml: |-
    filebeat.config:
      prospectors:
        # Mounted `filebeat-prospectors` configmap:
        path: ${path.config}/prospectors.d/*.yml
        # Reload prospectors configs as they change:
        reload.enabled: false
      modules:
        path: ${path.config}/modules.d/*.yml
        # Reload module configs as they change:
        reload.enabled: false

    processors:
      - add_cloud_metadata:

    cloud.id: ${ELASTIC_CLOUD_ID}
    cloud.auth: ${ELASTIC_CLOUD_AUTH}

    output.elasticsearch:
      hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
      username: ${ELASTICSEARCH_USERNAME}
      password: ${ELASTICSEARCH_PASSWORD}
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-prospectors
  namespace: kube-system
  labels:
    k8s-app: filebeat
    kubernetes.io/cluster-service: "true"
data:
  kubernetes.yml: |-
    - type: docker
      containers.ids:
      - "*"
      processors:
        - add_kubernetes_metadata:
            in_cluster: true
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: filebeat
  namespace: kube-system
  labels:
    k8s-app: filebeat
    kubernetes.io/cluster-service: "true"
spec:
  template:
    metadata:
      labels:
        k8s-app: filebeat
        kubernetes.io/cluster-service: "true"
    spec:
      serviceAccountName: filebeat
      terminationGracePeriodSeconds: 30
      containers:
      - name: filebeat
        image: docker.elastic.co/beats/filebeat:6.2.4
        args: [
          "-c", "/etc/filebeat.yml",
          "-e",
        ]
        env:
        - name: ELASTICSEARCH_HOST
          value: elasticsearch
        - name: ELASTICSEARCH_PORT
          value: "9200"
        - name: ELASTICSEARCH_USERNAME
          value: elastic
        - name: ELASTICSEARCH_PASSWORD
          value: changeme
        - name: ELASTIC_CLOUD_ID
          value:
        - name: ELASTIC_CLOUD_AUTH
          value:
        securityContext:
          runAsUser: 0
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 100Mi
        volumeMounts:
        - name: config
          mountPath: /etc/filebeat.yml
          readOnly: true
          subPath: filebeat.yml
        - name: prospectors
          mountPath: /usr/share/filebeat/prospectors.d
          readOnly: true
        - name: data
          mountPath: /usr/share/filebeat/data
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      volumes:
      - name: config
        configMap:
          defaultMode: 0600
          name: filebeat-config
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: prospectors
        configMap:
          defaultMode: 0600
          name: filebeat-prospectors
      - name: data
        emptyDir: {}
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: filebeat
subjects:
- kind: ServiceAccount
  name: filebeat
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: filebeat
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: filebeat
  labels:
    k8s-app: filebeat
rules:
- apiGroups: [""] # "" indicates the core API group
  resources:
  - namespaces
  - pods
  verbs:
  - get
  - watch
  - list
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: filebeat
  namespace: kube-system
  labels:
    k8s-app: filebeat
---

如上,看起來似乎挺復雜,可以分為如下幾個部分:

  • ConfigMap
  • DaemonSet
  • ClusterRoleBinding
  • ClusterRole
  • ServiceAccount

很熟悉是吧,如果你還不清楚這些概念,請戳《從零開始搭建Kubernetes集群(四、搭建K8S Dashboard)》

ConfigMap

我們先重點關注一下DaemonSet的volumeMountsvolumes,以了解ConfigMap的掛載方式:

        volumeMounts:
        - name: config
          mountPath: /etc/filebeat.yml
          readOnly: true
          subPath: filebeat.yml
        - name: prospectors
          mountPath: /usr/share/filebeat/prospectors.d
          readOnly: true
        - name: data
          mountPath: /usr/share/filebeat/data
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      volumes:
      - name: config
        configMap:
          defaultMode: 0600
          name: filebeat-config
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: prospectors
        configMap:
          defaultMode: 0600
          name: filebeat-prospectors
      - name: data
        emptyDir: {}

如上,volumeMounts包括四個部分,解釋如下:

  • config
    filebeat-config這個Configmap會生成一個filebeat.yml文件,其會被掛載為Filebeat的配置文件/etc/filebeat.yml
  • prospectors
    prospectors這個Configmap會生成一個kubernetes.yml文件,其會被掛載到路徑/usr/share/filebeat/prospectors.d下,并被filebeat.yml引用
  • data
    Filebeat自身的數據掛載為emptyDir: {}
  • varlibdockercontainers
    K8S集群的日志都存儲在/var/lib/docker/containers,Filebeat將從該路徑進行收集

了解了ConfigMap的掛載方式后,現在,我們分析第一個ConfigMap:

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-config
  namespace: kube-system
  labels:
    k8s-app: filebeat
    kubernetes.io/cluster-service: "true"
data:
  filebeat.yml: |-
    filebeat.config:
      prospectors:
        # Mounted `filebeat-prospectors` configmap:
        path: ${path.config}/prospectors.d/*.yml
        # Reload prospectors configs as they change:
        reload.enabled: false
      modules:
        path: ${path.config}/modules.d/*.yml
        # Reload module configs as they change:
        reload.enabled: false

    processors:
      - add_cloud_metadata:

    cloud.id: ${ELASTIC_CLOUD_ID}
    cloud.auth: ${ELASTIC_CLOUD_AUTH}

    output.elasticsearch:
      hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
      username: ${ELASTICSEARCH_USERNAME}
      password: ${ELASTICSEARCH_PASSWORD}

我們知道,Configmap的每個key都會生成一個同名的文件,因此這里會創建一個配置文件filebeat.yml文件,其內容中的環境變量將由DaemonSet中的env部分定義。

filebeat.yml中,可以看到Filebeat的一個重要組件: prospectors(采礦者),其主要用來指定從哪些文件中采集數據。這里,prospectors并沒有直接指定目標文件,而是間接的引用路徑:${path.config}/prospectors.d/*.yml,由前面可知,該路徑中的yml文件由第二個ConfigMap定義:

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-prospectors
  namespace: kube-system
  labels:
    k8s-app: filebeat
    kubernetes.io/cluster-service: "true"
data:
  kubernetes.yml: |-
    - type: docker
      containers.ids:
      - "*"
      processors:
        - add_kubernetes_metadata:
            in_cluster: true

如上,type指定了prospectors的類型為docker,表示收集本機的docker日志。containers.ids*表示監聽所有容器。type除了docker,一般使用更多的是log,可以直接指定任何路徑上的日志文件,參見官方文檔

五、部署步驟

介紹完Filebeat的部署腳本后,我們開始真正的部署過程。

1.部署Filebeat

官方配置文件無法直接使用,需要我們定制。首先,修改DaemonSet中的環境變量env:

       env:
        - name: ELASTICSEARCH_HOST
          value: "X.X.X.X"
        - name: ELASTICSEARCH_PORT
          value: "9200"
        - name: ELASTICSEARCH_USERNAME
          value: 
        - name: ELASTICSEARCH_PASSWORD
          value: 
        - name: ELASTIC_CLOUD_ID
          value:
        - name: ELASTIC_CLOUD_AUTH
          value:

如上,ELASTICSEARCH_HOST指定為Elasticsearch集群的入口地址,端口ELASTICSEARCH_PORT為默認的9200;由于我的集群沒有加密,因此ELASTICSEARCH_USERNAMEELASTICSEARCH_PASSWORD全部留空,大家可以酌情修改;其他保持默認。

同時,還需要注釋掉第一個ConfigMap中output.elasticsearch的用戶名和密碼:

    output.elasticsearch:
      hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
      #username: ${ELASTICSEARCH_USERNAME}
      #password: ${ELASTICSEARCH_PASSWORD}

其次,還需要修改第二個ConfigMap的data部分為:

data:
  kubernetes.yml: |-
    - type: log
      enabled: true
      paths:
         - /var/log/*.log
    - type: docker
      containers.ids:
      - "*"
      processors:
        - add_kubernetes_metadata:
            in_cluster: true

如上,type: docker的配置可以對K8S上所有Docker容器產生的日志進行收集。另外,為了收集宿主機系統日志和K8S自身日志,我們還需要獲取/var/log/*.log

修改并創建完畢后,查看DaemonSet信息,如下圖所示:

[root@k8s-node1 filebeat]# kubectl get ds -n kube-system
NAME          DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR                     AGE
calico-etcd   1         1         1         1            1           node-role.kubernetes.io/master=   5d
calico-node   3         3         3         3            3           <none>                            5d
filebeat      2         2         0         2            0           <none>                            24s
kube-proxy    3         3         3         3            3           <none>                            5d

查看pod信息,每個節點都會啟動一個filebeat容器:

filebeat-hr5vq                            1/1       Running            1          3m        192.168.169.223   k8s-node2
filebeat-khzzj                            1/1       Running            1          3m        192.168.108.7     k8s-node3
filebeat-rsnbl                            1/1       Running            0          3m        192.168.36.126    k8s-node1

2.部署Kibana

參考官方示例,我們按需修改為如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana-logging
  namespace: kube-system
  labels:
    k8s-app: kibana-logging
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: kibana-logging
  template:
    metadata:
      labels:
        k8s-app: kibana-logging
    spec:
      containers:
      - name: kibana-logging
        image: docker.elastic.co/kibana/kibana:6.2.4
        resources:
          # need more cpu upon initialization, therefore burstable class
          limits:
            cpu: 1000m
          requests:
            cpu: 100m
        env:
          - name: ELASTICSEARCH_URL
            value: http://X.X.X.X:9200
        ports:
        - containerPort: 5601
          name: ui
          protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: kibana-logging
  namespace: kube-system
  labels:
    k8s-app: kibana-logging
spec:
  type: NodePort
  ports:
  - port: 5601
    targetPort: 5601
  selector:
    k8s-app: kibana-logging

如上,Kibana的版本為6.2.4,并且一定要與Filebeat、Elasticsearch保持一致。另外,注意將Deployment中env的環境變量ELASTICSEARCH_URL,修改為自己的Elasticsearch集群地址。

這里我們使用了Service暴露了NodePort,當然也可以使用Ingress,請參見《從零開始搭建Kubernetes集群(五、搭建K8S Ingress)》

3.訪問Kibana

好了,現在我們可以通過NodeIp:NodePort或Ingress方式來訪問Kibana。在配置Elasticsearch索引前綴后,即可檢索日志:


image.png

如上,可以看到K8S中各個容器的日志,當然也包括宿主機的系統日志。

4.測試應用日志

至此,我們通過Filebeat成功獲取了K8S上的容器日志以及系統日志。但在實際中,我們更關注的是應用程序的業務日志。這里,我們編寫一個簡單的JAVA項目來測試一下。

測試代碼

只是簡單的循環輸出遞增序列:


image.png

logback.xml

appender指定為STDOUT即可:


image.png

Dockerfile

可以使用gradle將項目發布為tar包,然后拷貝到java:9-re鏡像中。在build鏡像后,記得別忘記上傳至自己的倉庫中:

image.png

K8S部署腳本

執行該腳本即可完成測試項目的部署:


image.png

輸出日志

我們可以去/var/lib/docker/containers/下查看測試項目輸出的json格式日志:

image.png

在Dashborad中,也可以查看標準輸出的日志:


image.png

好了,我們已經成功的通過Filebeat上傳了自定義的應用程序日志,收工~

七、廢話

至此,K8S集群日志的監控方法就已介紹完畢。如果大家有興趣,還可嘗試使用Fluentd代替Filebeat,原理類似。

本人水平有限,難免有錯誤或遺漏之處,望大家指正和諒解,歡迎評論留言。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容