Kubernetes EFK 日志收集

日志收集架构

Kubernetes集群本身不提供收集日志的解决方案,目前基于ELK日志收集的方案主要有三种

  • 在节点运行一个agent收集日志
  • 在Pod中包含一个sidecar容器来收集日志 (可以参考K8s容器日志实时收集FileBeat+ES+Kibana)
  • 直接通过应用程序将日志信息推送到采集后端 (例kafka,es等)

1、节点级别的日志记录

节点日志采集

KubeSphere 日志图表 kubernetes日志收集_EFK

通过在每个节点上运行一个日志收集的Agent来采集数据,日志采集agent是一种专用工具,用于将日志数据推送到统一的后端。一般来说,这种agent用一个容器来运行,可以访问该节点上所有应用程序容器的日志文件所在目录

由于这种agent必须在每个节点上运行,所以需要使用DaemonSet控制器运行该应用程序。在节点运行agent,不需要对节点上运行的应用程序进行更改,对应用程序没有任何侵入性,但是这种方法也仅仅适合于日志输出到stdout和stderr的应用程序日志。容器情况下就会把这些日志输出到宿主机上的一个JSON文件之中,同样也可以通过docker log或者kubectl logs查看对应的日志,但是如果是直接写入到文件中,则并不是输出到stdout中并不能通过命令行查看到

优点: 每个Node仅需部署一个日志收集程序,资源消耗少,对应用无入侵

因为使用stdout的方式,只需要在宿主机上收集每个容器中的日志/var/log和/var/lib/docker/containers (目录要根据docker info中的dir进行修改)

容器会将日志转化为JSON格式,是docker中的配置起的作用。

配置如下

[root@k8s-01 ~]# docker info
...
Logging Driver: json-file
...

2、集群级日志架构

sidecar容器收集日志,如果我们的应用程序的日志是输出到容器中的某个日志文件的话,使用agent节点的方式是无法采集到的

对于容器输出到文件的方式可以直接在Pod中启动另外一个sidecar容器(例如filebeat)

使用sidecar有两种方式

  • 通过sidecar容器将应用程序中的日志进行重定向打印,把日志输出到stdout或者stderr
  • 使用sidecar运行日志采集agent

我个人认为,第一种方案和第二种方案都有优缺点,首先说第一种方案缺点是要重定向日志,并且如果日志分的比较细,比如Tomcat启动日志和业务日志是分开的,用第一种就没有优势。第二种缺点就是浪费资源,因为每次启动除了业务日志,还有sidecar日志要跟随业务启动。

第一种方式

KubeSphere 日志图表 kubernetes日志收集_EFK_02

使用sidecar将应用程序日志重定向就可以在接入前面agent采集自动获取日志信息,不需要在配置其他。虽然这个方式可以通过sidecar将应用程序日志转化为stdout输出,但是有一个明显的缺点,就是日志不仅会在原容器文件保留,还会通过stdout输出后占用磁盘空间,这样无形中就增加了一倍磁盘空间

下面实例是通过sidecar将容器本地的日志重定向到stdout中

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$(date) INFO $i" >> /var/log/1.log;     #模拟Tomcat将日志追加到文件中
        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']   #使用tail -f 打印日志
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  volumes:
  - name: varlog
    emptyDir: {}

创建完成后,我们就可以通过stdout查看到日志

[root@k8s-01 test]# kubectl logs counter count-log-1 
Fri May  1 18:33:26 UTC 2020 INFO 0
Fri May  1 18:33:27 UTC 2020 INFO 1
Fri May  1 18:33:28 UTC 2020 INFO 2
Fri May  1 18:33:29 UTC 2020 INFO 3
Fri May  1 18:33:30 UTC 2020 INFO 4
Fri May  1 18:33:31 UTC 2020 INFO 5
Fri May  1 18:33:32 UTC 2020 INFO 6

这时候我们可以在对应Pod宿主机上找到日志文件路径

[root@k8s-04 2db38c31797ef8830486739d7098ee81261fd36b32b6c6959a0f1cf014ab7f68]# pwd
/var/lib/docker/containers/2db38c31797ef8830486739d7098ee81261fd36b32b6c6959a0f1cf014ab7f68
 
 
#前面的日志路径,对应的就是容器ID
 
[root@k8s-04 2db38c31797ef8830486739d7098ee81261fd36b32b6c6959a0f1cf014ab7f68]# docker ps
CONTAINER ID        IMAGE                                                        COMMAND                  CREATED             STATUS              PORTS               NAMES
2db38c31797e        busybox                                                      "/bin/sh -c 'tail -n…"   6 minutes ago       Up 6 minutes                            k8s_count-log-1_counter_default_8c341c58-ec9c-4266-a190-779d514fadf0_0
77b4c4933cd1        busybox                                                      "/bin/sh -c 'i=0; wh…"   6 minutes ago       Up 6 minutes                            k8s_count_counter_default_8c341c58-ec9c-4266-a190-779d514fadf0_0
0c7b3e8d7e48        registry.cn-beijing.aliyuncs.com/abcdocker/pause-amd64:3.1   "/pause"                 6 minutes ago       Up 6 minutes                            k8s_POD_counter_default_8c341c58-ec9c-4266-a190-779d514fadf0_0

第二种方式

KubeSphere 日志图表 kubernetes日志收集_KubeSphere 日志图表_03

如果觉得第一种过于麻烦,会保留多个日志则可以使用以sidecar运行日志收集直接将日志推送到es中。

但是sidecar容器中运行日志采集代理程序会导致大量资源消耗,因为每个Pod都需要启动2个容器,业务容器和采集代理程序,另外无法使用kubectl logs命令来访问这些日志,因为他们不受kubelet控制

下面是Kubernetes官方的一个fluentd的配置文件实例,使用Configmap保存配置文件,通过Pod中启动sidecar容器代理采集agent将日志进行收集然后上传,当然我们也可以使用logstash、filebeat等代替

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
data:
  fluentd.conf: |
    <source>
      type tail
      format none
      path /var/log/1.log
      pos_file /var/log/1.log.pos
      tag count.format1
    </source>
 
    <source>
      type tail
      format none
      path /var/log/2.log
      pos_file /var/log/2.log.pos
      tag count.format2
    </source>
 
    <match **>
      type google_cloud
    </match>

上面的配置文件是收集原文件/var/log/1.log/var/log/2.log的日志数据,然后通过google_cloud这个插件将数据推送到Stackdriver后端去 (也可以推送到es和kafka)

上面是创建了fluentd的配置文件,下面在Deployment应用中添加2个容器即可

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-agent
    image: k8s.gcr.io/fluentd-gcp:1.30
    env:
    - name: FLUENTD_ARGS
      value: -c /etc/fluentd-config/fluentd.conf
    volumeMounts:
    - name: varlog
      mountPath: /var/log
    - name: config-volume
      mountPath: /etc/fluentd-config
  volumes:
  - name: varlog
    emptyDir: {}
  - name: config-volume
    configMap:
      name: fluentd-config

Pod创建完成后,容器count-agent就会从count容器中的日志进行收集然后上传。

通过应用程序收集日志

KubeSphere 日志图表 kubernetes日志收集_EFK_04

除了上面通过宿主机运行agent采集和在容器中创建sidecar外,还有一种方案就是在代码层面实现,通过代码层面直接将日志输出到对应的后端存储

更多内容可以参阅官方文档https://v1-15.docs.kubernetes.io/zh/docs/concepts/cluster-administration/logging/

传统架构图

KubeSphere 日志图表 kubernetes日志收集_EFK_05


本次使用stdout的方式获取日志

  • Kubernetes 1.18
  • Elasticsearch 7.6.2
  • Fluend 3.0.1
  • Kibana 7.6.2

本次环境项目地址: https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/fluentd-elasticsearch

本次使用镜像均同步至阿里云

docker.elastic.co/elasticsearch/elasticsearch:7.6.2
docker.elastic.co/kibana/kibana:7.6.2
quay.io/fluentd_elasticsearch/fluentd:v3.0.1
 
同步如下 (有需要直接pull tag标记,或者将yaml中镜像修改下方即可)
registry.cn-beijing.aliyuncs.com/abcdocker/fluentd:v3.0.1
registry.cn-beijing.aliyuncs.com/abcdocker/elasticsearch:7.6.2
registry.cn-beijing.aliyuncs.com/abcdocker/kibana:7.6.2

安装Elasticsearch

如果已经有ES集群,这里可以不用安装。或者您不想在Kubernetes集群内安装ES,可以直接参考下面的文章。直接在宿主机上安装,和在kubernetes效果一样的。


Kubernetes StatefulSet允许我们为Pod分配一个稳定的标识和持久化存储,Elasticsearch需要稳定的存储来保证Pod在重新调度或者重启后的数据依旧不变,所以要试用StatefulSet来管理Pod

我这里使用NFS进行模式后端存储,当然使用Ceph或者其他存储也是可以的

#创建命名空间
kubectl create namespace elk

#使用StatefulSet创建ES集群
wget down.i4t.com/kubernetes/es/es.yaml
kubectl apply -f es.yaml

创建es svc

#这里的service使用无头服务
wget down.i4t.com/kubernetes/es/es-svc.yaml
kubect apply -f es-svc.yaml

es.yaml配置文件参数解释

[root@k8s-01 elk]# cat es.yaml 
apiVersion: apps/v1
kind: StatefulSet   #使用statefulset创建Pod
metadata:
  name: es-test          #pod名称,使用statefulSet创建的Pod是有序号有顺序的
  namespace: elk    #命名空间
spec:
  serviceName: elasticsearch  #与svc相关联,这可以确保使用以下DNS地址访问Statefulset中的每个pod (es-cluster-[0,1,2].elasticsearch.elk.svc.cluster.local)
  replicas: 3   #副本数量
  selector:
    matchLabels:
      app: elasticsearch    #和pod template配置的labels相匹配
  template:
    metadata:
      labels: 
        app: elasticsearch
    spec:
      #nodeSelector:    #如果需要匹配几台对应的节点可以添加nodeSelect
      #  es: log
      initContainers:   #容器初始化前的操作
      - name: increase-vm-max-map   
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sysctl", "-w", "vm.max_map_count=262144"]  #添加mmap计数限制,太低可能造成内存不足的错误
        securityContext:    #仅应用到指定的容器上,并且不会影响Volume
          privileged: true  #运行特权容器
      - name: increase-fd-ulimit
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sh", "-c", "ulimit -n 65536"] #修改文件描述符最大数量
        securityContext:
          privileged: true
      containers:
      - name: elasticsearch
        image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
        imagePullPolicy: IfNotPresent
        ports:
        - name: rest
          containerPort: 9200   #Restapi
        - name: inter
          containerPort: 9300  #节点通信
        resources:
          limits:
            cpu: 1000m   #限制cpu (虚拟环境可以修改小一点)
          requests:
            cpu: 1000m
        volumeMounts:   #容器挂载
        - name: data    #名称
          mountPath: /usr/share/elasticsearch/data  #挂载点
        env:    #设置相关环境变量
        - name: cluster.name  #es集群的名称
          value: k8s-logs
        - name: node.name   #节点名称
          valueFrom:  #通过匹配上面metadata.name来当节点的名称
            fieldRef:
              fieldPath: metadata.name
        - name: cluster.initial_master_nodes #初始化集群引导,需要是pod的名称
          value: "es-test-0,es-test-1,es-test-2"
        - name: discovery.zen.minimum_master_nodes  #节点数量,高可用集群至少3个主节点,其中2个至少不仅投票节点。
          value: "2"
        - name: discovery.seed_hosts #用于es集群中节点互相连接发现
          value: "elasticsearch"
        - name: ES_JAVA_OPTS  #设置Java的内存参数
          value: "-Xms512m -Xmx512m"
        - name: network.host   
          value: "0.0.0.0"
  volumeClaimTemplates:   #定义持久化模板
  - metadata:
      name: data
      labels:
        app: elasticsearch
    spec:
      accessModes: [ "ReadWriteOnce" ]  #访问模式
      storageClassName: managed-nfs-storage  #storageclass对象  (这里我使用nfs创建的storageclass,当然也可以修改为ceph或者本地存储)
      resources:
        requests:
          storage: 30Gi    #pv资源大小

es镜像比较大,如果下载比较慢可以通过我的镜像直接load导入

wget http://down.i4t.com/kubernetes/es/es.tar

相关参数文档,请阅读官网

https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html

文章中引用了NFS和storageclass,需要的可以前往

StorageClass 配置文档 持久化存储 StorageClass

NFS按照参考 Kubernetes PV与PVC

查看Pod IP测试Pod是否正常

[root@k8s-01 elk]# kubectl get pod -n elk -o wide
NAME   READY   STATUS    RESTARTS   AGE     IP             NODE     NOMINATED NODE   READINESS GATES
es-test-0   1/1     Running   7          4h5m    172.30.120.2   k8s-01
es-test-1   1/1     Running   0          6h20m   172.30.144.3   k8s-05
es-test-2   1/1     Running   0          6h19m   172.30.248.3   k8s-04

我们还可以查看一下pv和pvc是否正常创建和绑定

[root@k8s-01 ~]# kubectl  get pv,pvc -n elk
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                     STORAGECLASS          REASON   AGE
persistentvolume/pvc-3d622c74-74d5-4f75-934c-bfcdfc22f647   30Gi       RWO            Delete           Bound    elk/data-es-abcdocker-0   managed-nfs-storage            23h
persistentvolume/pvc-c83d1aad-031d-4b5b-90fd-1467f4fd9997   30Gi       RWO            Delete           Bound    elk/data-es-abcdocker-1   managed-nfs-storage            23h
persistentvolume/pvc-f85a272a-3e4f-4686-b15f-208008412320   30Gi       RWO            Delete           Bound    elk/data-es-abcdocker-2   managed-nfs-storage            23h
 
NAME                                        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
persistentvolumeclaim/data-es-abcdocker-0   Bound    pvc-3d622c74-74d5-4f75-934c-bfcdfc22f647   30Gi       RWO            managed-nfs-storage   23h
persistentvolumeclaim/data-es-abcdocker-1   Bound    pvc-c83d1aad-031d-4b5b-90fd-1467f4fd9997   30Gi       RWO            managed-nfs-storage   23h
persistentvolumeclaim/data-es-abcdocker-2   Bound    pvc-f85a272a-3e4f-4686-b15f-208008412320   30Gi       RWO            managed-nfs-storage   23h
[root@k8s-01 ~]#

使用无头服务创建service

解释: Headless Service 无头服务 该服务不会分配ClusterIP,也不会通过kube-proxy做反向代理和负载均衡。而是通过DNS提供稳定的网络ID来访问,DNS会将headless service的后端直接解析为PodIP列表,主要提供StatefulSet使用

[root@k8s-01 elk]# cat es-svc.yaml 
kind: Service
apiVersion: v1
metadata:
  name: elasticsearch
  namespace: elk
  labels:
    app: elasticsearch
spec:
  selector:
    app: elasticsearch
  clusterIP: None
  ports:
    - port: 9200
      name: rest
    - port: 9300
      name: inter-node

svc参数解释

指定匹配标签app=elasticsearch,当我们将elasticsearch statefulset与此服务关联时,服务将返回标签带有app=elasticsearch的Elasticsearch Pod的DNS A记录

查看创建完毕后的pod和svc

[root@k8s-01 elk]# kubectl apply -f es-svc.yaml 
[root@k8s-01 elk]# kubectl get all -n elk
NAME       READY   STATUS    RESTARTS   AGE
pod/es-test-0   1/1     Running   7          4h8m
pod/es-test-1   1/1     Running   0          6h23m
pod/es-test-2   1/1     Running   0          6h22m
 
NAME                    TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)             AGE
service/elasticsearch   ClusterIP   None                 9200/TCP,9300/TCP   12m
 
NAME                  READY   AGE
statefulset.apps/es   3/3     6h24m

es设置了无头服务和一个稳定的域名.elasticsearch.elk.svc.cluster.local

接下来可以获取到当前Pod节点的信息

[root@k8s-01 elk]# curl 172.30.248.3:9200
{
  "name" : "es-test-2",
  "cluster_name" : "k8s-logs",
  "cluster_uuid" : "UKHhz9ECQaCU3FN-t_sNKA",
  "version" : {
    "number" : "7.6.2",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f",
    "build_date" : "2020-03-26T06:34:37.794943Z",
    "build_snapshot" : false,
    "lucene_version" : "8.4.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

查看集群状态

$ curl 172.30.248.3:9200/_cluster/state?pretty |less
 
{
  "cluster_name" : "k8s-logs",
  "cluster_uuid" : "4IXFctNTT0-KONKlD2K-0Q",
  "version" : 40,
  "state_uuid" : "GUTDz1iKQayYyI_4AHHGIA",
  "master_node" : "SSOXZ5n5TFSpL12UtHhX-w",
  "blocks" : { },
  "nodes" : {
    "LA8ToV02TFOrFHGwVyzpeg" : {
      "name" : "es-abcdocker-1",
      "ephemeral_id" : "FYJWZKTrTg6aPRXaefFM8w",
      "transport_address" : "10.244.0.6:9300",
      "attributes" : {
        "ml.machine_memory" : "4128923648",
        "ml.max_open_jobs" : "20",
        "xpack.installed" : "true"
      }
    },
    "tBVCVr4ATzO5juMtgdTtSA" : {
      "name" : "es-abcdocker-0",
      "ephemeral_id" : "65TM1xFXRaGZISaNaV5mFA",
      "transport_address" : "10.244.2.11:9300",
      "attributes" : {
        "ml.machine_memory" : "4128923648",
        "ml.max_open_jobs" : "20",
        "xpack.installed" : "true"
      }
    },
    "SSOXZ5n5TFSpL12UtHhX-w" : {
      "name" : "es-abcdocker-2",
      "ephemeral_id" : "rQp2Bo0-RtWqG2scbGsc_w",
      "transport_address" : "10.244.1.5:9300",
      "attributes" : {
        "ml.machine_memory" : "4128923648",
        "xpack.installed" : "true",
        "ml.max_open_jobs" : "20"
      }
    }
  },
  "metadata" : {
    "cluster_uuid" : "4IXFctNTT0-KONKlD2K-0Q",
...
 
#可以查看到当前集群的数据结构

通过_cat/nodes?v查看集群中所有的节点,这里我们可以看到master节点是es-test-1 (es-test-1为pod名称)

[root@k8s-01 ~]# curl 10.244.1.5:9200/_cat/nodes?v
ip          heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
10.244.0.6            13          97   3    0.01    0.05     0.07 dilm      -      es-abcdocker-1
10.244.2.11           10          97   2    0.00    0.03     0.00 dilm      -      es-abcdocker-0
10.244.1.5             7          97   2    0.24    0.17     0.11 dilm      *      es-abcdocker-2

高可用性(HA)群集至少需要三个主节点,其中至少两个不是仅投票节点。即使其中一个节点发生故障,这样的群集也将能够选举一个主节点。


创建Kibana服务

和ES一样,kibana也是可以不在kubernetes集群中安装,同样也可以安装在集群外,集群外安装可以查阅之前的ELK文章,这里不过多说明。本次环境在kubernetes中安装ELK 二进制安装并收集nginx日志

[root@k8s-01 elk]# cat kibana.yaml 
apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: elk
  labels:
    app: kibana
spec:
  ports:
  - port: 5601
  type: NodePort
  selector:
    app: kibana
 
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: elk
  labels:
    app: kibana
spec:
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      containers:
      - name: kibana
        image: docker.elastic.co/kibana/kibana:7.6.2
        resources:
          limits:
            cpu: 200m
          requests:
            cpu: 200m
        env:
        - name: ELASTICSEARCH_HOSTS
          value: http://elasticsearch:9200    #由于是一个headless service,所以该域将解析为3个 Elasticsearch Pod的IP地址列表
        ports:
        - containerPort: 5601

创建Kibana

[root@k8s-01 elk]# kubectl apply -f  kibana.yaml 
service/kibana created
deployment.apps/kibana created

查看创建Pod状态

[root@k8s-01 elk]# kubectl get pod,svc -n elk
NAME                          READY   STATUS    RESTARTS   AGE
pod/es-test-0                 1/1     Running   0          61m
pod/es-test-1                 1/1     Running   0          62m
pod/es-test-2                 1/1     Running   0          62m
pod/kibana-8686d977d7-t9dc5   1/1     Running   0          74m
 
NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)             AGE
service/elasticsearch   ClusterIP   None                   9200/TCP,9300/TCP   74m
service/kibana          NodePort    10.254.252.9           5601:31882/TCP      74m

访问Kibana

任意集群节点:31882

KubeSphere 日志图表 kubernetes日志收集_elasticsearch_06

Fluentd

采集日志客户端采用Fluentd来进行采集日志,使用ConfigMap的形式来存储Fluentd的配置。

这次环境我们日志格式为stdout的方式,日志输出到每个宿主机的/var/log/containers/下面,所以我们Fluentd安装使用DaemonSet方式即可。

# 参数解释
 
kind: ConfigMap
apiVersion: v1
metadata:
  name: fluentd-config            
  namespace: elk                    #flunentd所属命名空间
data:
  system.conf: |-
    <system>
      root_dir /tmp/fluentd-buffers/      #系统日志信息
    </system>
  containers.input.conf: |-              #容器输入日志配置信息
 
 
#日志源配置
    <source>
      @id fluentd-containers.log         #表示引用该日志源的唯一标志符
      @type tail                         #Fluentd内置命令,tail表示Fluentd从上次读取的位置通过tail不断获取数据
      path /var/log/containers/*.log     #日志路径 (docker stdout默认输出路径,如果修改docker引擎配置这里也需要修改)
      pos_file /var/log/es-containers.log.pos  #检查点,将从文件中获取上一次检查数据的时间
      time_format %Y-%m-%dT%H:%M:%S.%NZ   #时间格式
      localtime
      tag raw.kubernetes.*               #用来将日志源与目标或者过滤器匹配的自定义字符串,Fluentd匹配源/目标标签
      format json                        #JSON解析器
      read_from_head true    
    </source>
 
 
路由配置
   
     <match **>             #标示一个目标,后面是一个匹配日志源的正则表达式,获取所有日志*.*并且全部发送给ES
      @id elasticsearch      #ID唯一标示符
      @type elasticsearch    #支持的输出插件标示符 (本选项为内置命令,除了输出到es,还可以输出到kafka、logstash等)
      @log_level info        #日志级别
      include_tag_key true    
      host elasticsearch    #es地址 (由于我们es为无头服务,地址就是elasticsearch)
      port 9200             #es端口
      logstash_format true    # es服务队日志数据构建反索引进行搜索,设置为true,fluentd将会以logstash格式来转发构建的日志数据
      request_timeout    30s   #超时时间
 
       <buffer>               #缓存配置,fluentd允许在目录不可用时进行缓存
        @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>

创建Fluentd配置文件,文件内容如下

cat >> fluentd-configmap.yaml <<EOF
kind: ConfigMap
apiVersion: v1
metadata:
  name: fluentd-config
  namespace: elk
data:
  system.conf: |-
    <system>
      root_dir /tmp/fluentd-buffers/
    </system>
  containers.input.conf: |-
    <source>
      @id fluentd-containers.log
      @type tail                              # Fluentd 内置的输入方式,其原理是不停地从源文件中获取新的日志。
      path /var/log/containers/*.log          # 挂载的服务器Docker容器日志地址
      pos_file /var/log/es-containers.log.pos
      tag raw.kubernetes.*                    # 设置日志标签
      read_from_head true
      <parse>                                 # 多行格式化成JSON
        @type multi_format                    # 使用 multi-format-parser 解析器插件
        <pattern>
          format json                         # JSON解析器
          time_key time                       # 指定事件时间的时间字段
          time_format %Y-%m-%dT%H:%M:%S.%NZ   # 时间格式
        </pattern>
        <pattern>
          format /^(?.+) (?stdout|stderr) [^ ]* (?.*)$/
          time_format %Y-%m-%dT%H:%M:%S.%N%:z
        </pattern>
      </parse>
    </source>
    # 在日志输出中检测异常,并将其作为一条日志转发 
    # https://github.com/GoogleCloudPlatform/fluent-plugin-detect-exceptions
     <match raw.kubernetes.**>          # 匹配tag为raw.kubernetes.**日志信息
      @id raw.kubernetes
      @type detect_exceptions           # 使用detect-exceptions插件处理异常栈信息
      remove_tag_prefix raw             # 移除 raw 前缀
      message log                       
      stream stream                     #stdout输出
      multiline_flush_interval 5
      max_bytes 500000                   #异常信息最大字节数
      max_lines 1000                     #异常信息最大行数
     </match>
 
      <filter **># 拼接日志
      @id filter_concat
      @type concat                # Fluentd Filter 插件,用于连接多个事件中分隔的多行日志。
      key message
      multiline_end_regexp /\n$/  # 以换行符“\n”拼接
      separator ""
     </filter>
 
    # 添加 Kubernetes metadata 数据
    <filter kubernetes.**>
      @id filter_kubernetes_metadata      #将获取到的日志转化,添加pod信息、命名空间,labels标签等
      @type kubernetes_metadata
    </filter>
 
    # JSON格式转换,如果原来是json格式,将把json格式内容从新进行转化
    # 插件地址:https://github.com/repeatedly/fluent-plugin-multi-format-parser
    <filter kubernetes.**>
      @id filter_parser
      @type parser                # multi-format-parser多格式解析器插件
      key_name log                # 在要解析的记录中指定字段名称。
      reserve_data true           # 在解析结果中保留原始键值对。
      remove_key_name_field true  # key_name 解析成功后删除字段。
      <parse>
        @type multi_format
        <pattern>
          format json
        </pattern>
        <pattern>
          format none
        </pattern>
       </parse>
    </filter>
      
    
 
    # 删除一些多余的属性
    <filter kubernetes.**>
      @type record_transformer
      remove_keys $.docker.container_id,$.kubernetes.container_image_id,$.kubernetes.pod_id,$.kubernetes.namespace_id,$.kubernetes.master_url,$.kubernetes.labels.pod-template-hash
    </filter>
 
    # 只保留具有logging=true标签的Pod日志,如果添加只会收集带有logging=true标签的pod
    #<filter kubernetes.**>
    #  @id filter_log
    #  @type grep
    #  <regexp>
    #    key $.kubernetes.labels.logging
    #    pattern ^true$
    #  </regexp>
    #</filter>
 
  ###### 监听配置,一般用于日志聚合用 ######
  forward.input.conf: |-
    # 监听通过TCP发送的消息
    <source>
      @id forward
      @type forward
    </source>
 
  output.conf: |-
    <match **>
      @id elasticsearch
      @type elasticsearch
      @log_level info
      include_tag_key true
      host elasticsearch
      port 9200
      logstash_format true
      logstash_prefix k8s  # 设置 index 前缀为 k8s
      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>
EOF

创建fluentd configmap,检查configmap状态

[root@k8s-01 ~]# kubectl  apply -f fluentd-configmap.yaml
configmap/fluentd-config created
 
[root@k8s-01 ~]# kubectl  get configmap  -n elk
NAME             DATA   AGE
fluentd-config   4      7s

接下来创建fluentd-daemonset

cat >fluentd-ds.yaml <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd-es
  namespace: elk
  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: elk
  apiGroup: ""
roleRef:
  kind: ClusterRole
  name: fluentd-es
  apiGroup: ""
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-es
  namespace: elk
  labels:
    k8s-app: fluentd-es
spec:
  selector:
    matchLabels:
      k8s-app: fluentd-es
  template:
    metadata:
      labels:
        k8s-app: fluentd-es
        kubernetes.io/cluster-service: "true"
      # 此注释确保如果节点被驱逐,fluentd不会被驱逐,支持关键的基于 pod 注释的优先级方案。
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ''
    spec:
      serviceAccountName: fluentd-es
      containers:
      - name: fluentd-es
        image: quay.io/fluentd_elasticsearch/fluentd:v3.0.1
        env:
        - name: FLUENTD_ARGS
          value: --no-supervisor -q
        resources:
          limits:
            memory: 500Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers      #docker日志存放路径 (默认情况下)
          readOnly: true
        - name: config-volume
          mountPath: /etc/fluent/config.d
      #nodeSelector:    #这里可以设置需要打标签的节点,我们目前只有3台都需要采集,所以可以注释这个标签
      #  beta.kubernetes.io/fluentd-ds-ready: "true"
      tolerations:
      - operator: Exists
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: config-volume
        configMap:
          name: fluentd-config
EOF

接下来创建

kubectl apply -f fluentd-ds.yaml

我们等待一会可以在es中看到fluentd收集的日志索引

[root@k8s-01 ~]# curl 10.244.0.6:9200/_cat/indices
green open .kibana_task_manager_1   X54N-pZATVmvH7ZbWTgQRw 1 1  2 2  40.3kb  20.1kb
green open .apm-agent-configuration NDxFtJcnTl-RjZEpRYnGIA 1 1  0 0    566b    283b
green open .kibana_1                DhJEAzOGTHqyOOLCoflmzw 1 1 10 1  62.1kb    31kb
green open k8s-2020.07.15           fqUmr9QeRMq4O29eZHV8LA 1 1 42 0 618.1kb 312.9kb
 
#其中命名为k8s-*的为我们下一步需要在kibana中添加的选项

访问kibana

[root@k8s-01 ~]# kubectl  get svc -n elk
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
elasticsearch   ClusterIP   None                    9200/TCP,9300/TCP   2d2h
kibana          NodePort    10.105.190.52           5601:31978/TCP      2d2h
 
#在浏览器中访问任意节点IP:31978端口

进入到kibana中,我们配置索引

设置-->索引-->配置索引

KubeSphere 日志图表 kubernetes日志收集_EFK_07

这里已经获取到我们在es中查看的索引了,点击下一步

KubeSphere 日志图表 kubernetes日志收集_k8s_08

在该页面中配置使用哪个字段按时间过滤日志数据,在下拉列表中,选择@timestamp字段,然后点击Create index pattern,创建完成后,点击左侧导航菜单中的Discover,然后就可以看到一些直方图和最近采集到的日志数据了

KubeSphere 日志图表 kubernetes日志收集_elasticsearch_09

添加完成后,我们点击左边Discover就可以看到数据

KubeSphere 日志图表 kubernetes日志收集_elasticsearch_10

这边还可以筛选过滤一下,只看几个字段的数据

KubeSphere 日志图表 kubernetes日志收集_elasticsearch_11


测试数据

cat<<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:alpine
        name: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  selector:
    app: nginx
  type: NodePort
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30001

接下来我们直接访问宿主机的IP:30001端口,查看nginx日志

[root@k8s-01 ~]# kubectl  get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1                443/TCP        2d6h
nginx        NodePort    10.102.132.199           80:30001/TCP   2d6h

KubeSphere 日志图表 kubernetes日志收集_日志收集_12