一、日志级别的简单介绍

日志打印的常见级别:

日志打印通常有四种级别,从高到底分别是:ERROR、WARN、INFO、DEBUG。应该选用哪种级别是个很重要的问题。

日志级别中的优先级是什么意思?在你的系统中如果开启了某一级别的日志后,就不会打印比它级别低的日志。例如,程序如果开启了INFO级别日志,DEBUG日志就不会打印,通常在生产环境中开启INFO日志。

 

1、DEBUG:

DEBU可以打印出最详细的日志信息,主要用于开发过程中打印一些运行信息。

2、INFO:

INFO可以打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。

3、WARNING:

WARNING 表明发生了一些暂时不影响运行的错误,会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示

4、ERROR:

ERROR 可以打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别,这一级就是比较重要的错误了,软件的某些功能已经不能继续执行了。

二、EFK介绍

EFK:

在Kubernetes集群上运行多个服务和应用程序时,日志收集系统可以帮助你快速分类和分析由Pod生成的大量日志数据。Kubernetes中比较流行的日志收集解决方案是Elasticsearch、Fluentd和Kibana(EFK)技术栈,也是官方推荐的一种方案。

 

Elasticsearch是一个实时的,分布式的,可扩展的搜索引擎,它允许进行全文本和结构化搜索以及对日志进行分析。它通常用于索引和搜索大量日志数据,也可以用于搜索许多不同种类的文档。

 

Elasticsearch通常与Kibana一起部署,kibana可以把Elasticsearch采集到的数据通过dashboard(仪表板)可视化展示出来。Kibana允许你通过Web界面浏览Elasticsearch日志数据,也可自定义查询条件快速检索出elasticccsearch中的日志数据。

 

Fluentd是一个流行的开源数据收集器,我们在 Kubernetes 集群节点上安装 Fluentd,通过获取容器日志文件、过滤和转换日志数据,然后将数据传递到 Elasticsearch 集群,在该集群中对其进行索引和存储。

三、安装部署EFK

PS: 当前使用的k8s集群版本为1.23,使用的容器运行时是docker,如果使用的是k8s版本为1.24之后,容器运行时是containerd则不使用于该版本的EFK部署,具体使安装fluentd时有所不同,看标题3.3.1内容。

3.1 安装Elasticsearch组件(安装es)

3.1.1 创建storageclass,实现存储类动态供给

安装es集群之前,由于es是要产生数据的,最好创建一个nfs持久化动态存储。

在k8s集群的所有节点都安装nfs服务

#所有节点安装nfs
[root@k8s-master ~]# yum -y install nfs-utils
[root@k8s-master ~]# systemctl enable nfs

#master节点创建存储路径
[root@k8s-master ~]# mkdir -p /data/es-1

#修改nfs的配置文件(*允许任何客户端挂载)
[root@k8s-master ~]# cat /etc/exports
/data/es-1 *(rw,no_root_squash)

#使配置文件生效并重启nfs服务
[root@k8s-master ~]# exportfs -arv
exporting *:/data/es-1
[root@k8s-master ~]# systemctl restart nfs

3.1.2 创建运行nfs-provisioner的sa账号并授权

#创建sa账号
[root@k8s-master ~]# kubectl create sa nfs-es
serviceaccount/nfs-es created

[root@k8s-master ~]# kubectl get sa | grep -E -i "name|nfs"
NAME      SECRETS   AGE
nfs-es    1         24s

#对sa账号做RBAC授权
[root@k8s-master ~]# kubectl create clusterrolebinding nfs-es-clusterrolebinding --clusterrole=cluster-admin --serviceaccount=default:nfs-es
clusterrolebinding.rbac.authorization.k8s.io/nfs-es-clusterrolebinding created

3.1.3 创建安装nfs-provisioner

这里使用的nfs-provisioner镜像版本是:registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0

[root@k8s-master ~]# cat deployment.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
  name: nfs-provisioner
spec:
  selector:
    matchLabels:
      app: nfs-provisioner
  replicas: 2
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-provisioner
    spec:
      serviceAccount: nfs-es
      containers:
        - name: nfs-provisioner
          image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: nfs-client-root         #将下面创建的卷挂载到容器的/persistentvolumes
              mountPath: /persistentvolumes
          env:                              #定义环境变量
            - name: PROVISIONER_NAME        #定义nfs的名称
              value: example.com/nfs        #注意这里定义的名称,后续创建存储类的时候名称也要保持一致
            - name: NFS_SERVER              #定义nfs服务端ip
              value: 192.168.57.131
            - name: NFS_PATH                #定义nfs服务端的共享路径
              value: /data/es-1
      volumes:                              #创建卷,定义卷名及nfs服务端ip和路径
        - name: nfs-client-root
          nfs:
            server: 192.168.57.131  
            path: /data/es-1
#应用
[root@k8s-master ~]# kubectl apply -f deployment.yaml 
deployment.apps/nfs-provisioner created

[root@k8s-master ~]# kubectl get deploy
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
nfs-provisioner   2/2     2            2           23s

3.1.4 创建存储类storageclass

由于es是由statefulset安装的,会产生三个pod,且每个pod都是需要存储的,所以需要创建一个存储类动态生成三个pv给到这三个pod去使用。

[root@k8s-master ~]# cat class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: es-storage
provisioner: example.com/nfs  #定义存储类的名称,这里要与创建nfs-provisioner的yaml文件中env定义的变量中保持一致。
#应用
[root@k8s-master ~]# kubectl apply -f class.yaml 
storageclass.storage.k8s.io/do-block-storage created

[root@k8s-master ~]# kubectl get storageclass
NAME         PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
es-storage   example.com/nfs   Delete          Immediate           false                  10s

3.1.5 通过statefulset创建elasticsearch集群

这里使用的es镜像版本为:elasticsearch:7.12.1

busybox的镜像版本为默认的:latest

3.1.6 创建名称空间及service

#创建名称空间
[root@k8s-master ~]# kubectl create ns efk-loging
namespace/efk-loging created

#创建service(该service有没有IP都可以,这里创建一个无IP的service)
[root@k8s-master ~]# cat elasticsearch_svc.yaml
kind: Service
apiVersion: v1
metadata:
  name: elasticsearch
  namespace: efk-loging
  labels:
    app: elasticsearch
spec:
  selector:
    app: elasticsearch
  clusterIP: None       #定义该service没有IP
  ports:
    - port: 9200
      name: rest
    - port: 9300
      name: inter-node
#应用
[root@k8s-master ~]# kubectl apply -f elasticsearch_svc.yaml 
service/elasticsearch created

[root@k8s-master ~]# kubectl get svc -A | grep -E "NAME|loging"
NAMESPACE     NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                  AGE
efk-loging    elasticsearch        ClusterIP   None            <none>        9200/TCP,9300/TCP        3m

3.1.7 创建es

这里使用statefulset控制器创建es的pod

#创建statefulset资源
[root@k8s-master ~]# cat elasticsearch-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: es-cluster
  namespace: efk-loging
spec:
  serviceName: elasticsearch     #定义创建好的svc名称
  replicas: 3
  selector:
    matchLabels:
      app: elasticsearch         #定义标签
  template:
    metadata:
      labels:
        app: elasticsearch       #定义标签,与上方标签保持一致
    spec:
      containers:
      - name: elasticsearch
        image: elasticsearch:7.12.1
        imagePullPolicy: IfNotPresent
        resources:              #定义资源限制(目标节点最多1核cpu、最少0.1核cpu可用,才能将pod调度过去)
            limits:
              cpu: 1000m
            requests:
              cpu: 100m
        ports:
        - containerPort: 9200
          name: rest
          protocol: TCP
        - containerPort: 9300
          name: inter-node
          protocol: TCP
        volumeMounts:
        - name: data            #挂载下方创建的data卷到容器中的/usr/share/elasticsearch/data
          mountPath: /usr/share/elasticsearch/data
        env:                    #定义es的集群的环境变量
          - name: cluster.name  #定义es集群的名称为 k8s-logs
            value: k8s-logs
          - name: node.name     #定义node节点的名称,通过下方定义的metadata.name字段获取
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: discovery.seed_hosts      #定义集群中的主机列表,这里定义的是pod名称,es-cluster-0是上面创建的StatefulSet定义的name,0/1/2数字是有序的生成pod的数字,elasticsearch是sevice的名称,efk-loging是名称空间,后面的是默认后缀
            value: "es-cluster-0.elasticsearch.efk-loging.svc.cluster.local,es-cluster-1.elasticsearch.efk-loging.svc.cluster.local,es-cluster-2.elasticsearch.efk-loging.svc.cluster.local"
          - name: cluster.initial_master_nodes   #定义集群列表,也可以按照如下方式去定义只写pod名称,不用按照上方那样写完成域名那么长。
            value: "es-cluster-0,es-cluster-1,es-cluster-2"
          - name: ES_JAVA_OPTS
            value: "-Xms512m -Xmx512m"
      initContainers:                #定义初始化容器,使用的是busybox镜像,同一个pod中定义多个容器,这些容器共享的是同一个pod资源,所以初始化容器修改的这些值,上方主容器也会生效。
      - name: fix-permissions        #第一个容器用来调整修复权限,将data挂载到/usr/share/elasticsearch/data并修改属主属组为1000
        image: busybox               #1000表示,如果一个服务以pod运行,1000就是服务的本身,改完之后上方定义的主容器挂载的该路径也会同步修改,es就可以往这个路径中写数据了。
        imagePullPolicy: IfNotPresent
        command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
        securityContext:
          privileged: true
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
      - name: increase-vm-max-map    #第二个容器调整vm.max的值
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sysctl", "-w", "vm.max_map_count=262144"]
        securityContext:
          privileged: true
      - name: increase-fd-ulimit     #第三个容器修改ulimit的值
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sh", "-c", "ulimit -n 65536"]
        securityContext:
          privileged: true
  volumeClaimTemplates:        #创建卷
  - metadata:
      name: data
      labels:
        app: elasticsearch
    spec:                      #创建卷申请模板,使用创建好的存储类并设置为ReadWriteOnce格式,大小为10G
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: es-storage    #这个存储类的名称不要写错
      resources:
        requests:
          storage: 10Gi
#创建StatefulSet,通过这里定义的卷申请模板找到定义的存储类es-storage,然后存储类找到nfs-provisioner,然后在nfs-provisioner所部署的节点上(也就是master节点)指定的共享路径(/data/es-1)中创建pv,然后这个yaml文件中定义的三个pod都会基于/data/es-1共享目录下创建三个目录,将这三个目录分别做成pvc,每个pvc的大小为10G
#应用
[root@k8s-master ~]# kubectl apply -f elasticsearch-statefulset.yaml 
statefulset.apps/es-cluster created

[root@k8s-master ~]# kubectl get pods -n efk-loging
NAME           READY   STATUS    RESTARTS   AGE
es-cluster-0   1/1     Running   0          22s
es-cluster-1   1/1     Running   0          15s
es-cluster-2   1/1     Running   0          8s

[root@k8s-master ~]# kubectl get statefulset -n efk-loging
NAME         READY   AGE
es-cluster   3/3     2m4s

3.2 安装kibana 可视化UI界面

这里使用多个kibana的镜像版本为:kibana:7.12.1

#定义yaml文件,创建kibana的service及
[root@k8s-master ~]# cat kibana.yaml
apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: efk-loging
  labels:
    app: kibana
spec:
  type: NodePort
  ports:
  - port: 5601
  selector:
    app: kibana
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: efk-loging
  labels:
    app: kibana
spec:
  replicas: 2
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      containers:
      - name: kibana
        image: kibana:7.12.1
        imagePullPolicy: IfNotPresent
        resources:
          limits:
            cpu: 1000m
          requests:
            cpu: 100m
        env:                           #定义环境变量,将es的数据存储,指定es的域名地址(通过kubectl get svc 查看es的网络域名)
          - name: ELASTICSEARCH_URL
            value: http://elasticsearch.efk-loging.svc.cluster.local:9200
        ports:
        - containerPort: 5601
#应用
[root@k8s-master ~]# kubectl apply -f kibana.yaml
service/kibana created
deployment.apps/kibana created

[root@k8s-master ~]#  kubectl get svc -A | egrep -i "name|kibana"
NAMESPACE     NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                  AGE
efk-loging    kibana               NodePort    10.96.72.136    <none>        5601:32464/TCP           5s

[root@k8s-master ~]# kubectl get deploy -A | egrep -i "name|kibana"
NAMESPACE     NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
efk-loging    kibana                    2/2     2            2           26s

[root@k8s-master ~]# kubectl get pods -A | egrep -i "name|kibana"
NAMESPACE     NAME                                       READY   STATUS    RESTARTS        AGE
efk-loging    kibana-8dd84cd56-7g976                     1/1     Running   0               32s
efk-loging    kibana-8dd84cd56-x65lk                     1/1     Running   0               32s

#浏览器访问集群内任意节点的IP地址加service映射的物理机端口32464,如下图
#目前是没有数据的,需要安装fluentd组件收集日志,然后将fluentd采集的数据交给es存储并由kibana展示出来。

基于k8s构建EFK+logstash+kafka日志平台_ELK

3.3 安装fluentd(docker做容器运行时)

同上,fluentd也是以pod形式安装,由于需要fluentd收集日志,所以需要在k8s集群中的每个节点都要安装包括控制节点,这里使用DaemonSet控制器部署fluentd,确保让k8s集群中的每个节点都运行fluentd。

 

这里使用的fluentd的镜像版本为:fluentd:v1.9.1-debian-1.0

#定义yaml,创建sa账号并做RBAC的授权及创建daemonset的控制器
[root@k8s-master ~]# cat fluentd.yaml
apiVersion: v1                            #定义sa账号
kind: ServiceAccount
metadata:
  name: fluentd
  namespace: efk-loging
  labels:
    app: fluentd
---
apiVersion: rbac.authorization.k8s.io/v1 #创建clusterrole并授予其对pod及namespace具有get、list、watch的权限
kind: ClusterRole
metadata:
  name: fluentd
  labels:
    app: fluentd
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - namespaces
  verbs:
  - get
  - list
  - watch
---
kind: ClusterRoleBinding                 #创建clusterrolebinding将sa账号绑定到clusterrole,使sa账号有用clusterrole的权限
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: fluentd
roleRef:
  kind: ClusterRole
  name: fluentd
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: fluentd
  namespace: efk-loging
---
apiVersion: apps/v1                      #创建daemonset
kind: DaemonSet
metadata:
  name: fluentd
  namespace: efk-loging
  labels:
    app: fluentd
spec:
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      serviceAccount: fluentd
      serviceAccountName: fluentd
      tolerations:                      #定义容忍度,能够调度到master节点
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd
        image: fluentd:v1.9.1-debian-1.0
        imagePullPolicy: IfNotPresent
        env:                            #定义环境变量,指定es的域名地址及端口
          - name:  FLUENT_ELASTICSEARCH_HOST
            value: "elasticsearch.efk-loging.svc.cluster.local"
          - name:  FLUENT_ELASTICSEARCH_PORT
            value: "9200"
          - name: FLUENT_ELASTICSEARCH_SCHEME
            value: "http"
          - name: FLUENTD_SYSTEMD_CONF
            value: disable
        resources:                      #定义资源限制,最多使用512内存,调度的目标节点最少要有0.1核的cpu及200M的内存。
          limits:
            memory: 512Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:                   #挂载下方创建的卷到pod中
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:                          #创建卷
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
#应用
[root@k8s-master ~]# kubectl apply -f fluentd.yaml 
serviceaccount/fluentd created
clusterrole.rbac.authorization.k8s.io/fluentd created
clusterrolebinding.rbac.authorization.k8s.io/fluentd created
daemonset.apps/fluentd created

[root@k8s-master ~]# kubectl get pods -A -owide | egrep -i "name|fluentd"
NAMESPACE     NAME                                       READY   STATUS    RESTARTS        AGE   IP                NODE          NOMINATED NODE   READINESS GATES
efk-loging    fluentd-9w7m7                              1/1     Running   0               9s    193.168.194.81    k8s-worker1   <none>           <none>
efk-loging    fluentd-q289z                              1/1     Running   0               9s    193.168.235.203   k8s-master    <none>           <none>
efk-loging    fluentd-thmw6                              1/1     Running   0               9s    193.168.126.17    k8s-worker2   <none>           <none>

3.3.1 使用containerd做容器运行时安装fluentd

这里使用的fluentd的镜像版本为:docker.io/fluent/fluentd-kubernetes-daemonset:v1.16-debian-elasticsearch7-1

 

与docker做容器运行时的fluentd.yaml文件基本一致,如下所示增加了env的变量及修改了镜像版本两个位置,至于下方配置kibana则完全一样。

[root@k8s-master ~]# cat fluentd.yaml
apiVersion: v1                          
kind: ServiceAccount
metadata:
  name: fluentd
  namespace: efk-loging
  labels:
    app: fluentd
---
apiVersion: rbac.authorization.k8s.io/v1 
kind: ClusterRole
metadata:
  name: fluentd
  labels:
    app: fluentd
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - namespaces
  verbs:
  - get
  - list
  - watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: fluentd
roleRef:
  kind: ClusterRole
  name: fluentd
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: fluentd
  namespace: efk-loging
---
apiVersion: apps/v1                    
kind: DaemonSet
metadata:
  name: fluentd
  namespace: efk-loging
  labels:
    app: fluentd
spec:
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      serviceAccount: fluentd
      serviceAccountName: fluentd
      tolerations:                    
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd
        image: docker.io/fluent/fluentd-kubernetes-daemonset:v1.16-debian-elasticsearch7-1  #修改了镜像版本信息
        imagePullPolicy: IfNotPresent
        env:                            #定义环境变量,指定es的域名地址及端口
          - name:  FLUENT_ELASTICSEARCH_HOST
            value: "elasticsearch.efk-loging.svc.cluster.local"
          - name:  FLUENT_ELASTICSEARCH_PORT
            value: "9200"
          - name: FLUENT_ELASTICSEARCH_SCHEME
            value: "http"
          - name: FLUENTD_SYSTEMD_CONF
            value: disable
          - name: FLUENT_CONTAINER_TAIL_PARSER_TYPE         #增加定义从containerd收集日志(下方定义了cri)
            value: "cri"
          - name: FLUENT_CONTAINER_TAIL_PARSER_TIME_FORMAT  #增加定义年月日时分秒信息
            value: "%Y-%m-%dT%H:%M:%S.%L%z"

        resources:                 
          limits:
            memory: 512Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:               
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:                       
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

3.3.2 配置kibana

安装fluentd完成之后,在kibana的界面上配置,如下图

基于k8s构建EFK+logstash+kafka日志平台_Linux_02

创建索引

基于k8s构建EFK+logstash+kafka日志平台_ELK_03

基于k8s构建EFK+logstash+kafka日志平台_K8s_04

基于k8s构建EFK+logstash+kafka日志平台_K8s_05

创建时间戳,选择tiemstamp并创建

基于k8s构建EFK+logstash+kafka日志平台_Linux_06

基于k8s构建EFK+logstash+kafka日志平台_Linux_07

点击左侧Discover查看日志

基于k8s构建EFK+logstash+kafka日志平台_K8s_08

基于k8s构建EFK+logstash+kafka日志平台_Linux_09

3.3.3 验证efk收集新建业务pod日志

#创建pod
[root@k8s-master ~]# cat pod.yaml  //写入如下内容


apiVersion: v1
kind: Pod
metadata:
  name: test
spec:           #使用busybox创建一个pod,并将物理机的时间挂载到pod中使pod中的时间与物理机的时间保持一致,且执行命令每三秒输出一次时间
  containers:
  - name: count
    image: busybox
    imagePullPolicy: IfNotPresent
    args: [/bin/sh, -c,'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 3; done']
    volumeMounts:
    - name: log-time
      mountPath: /etc/localtime
  volumes:
    - name: log-time
      hostPath:
        path: /usr/share/zoneinfo/Asia/Shanghai


#应用
[root@k8s-master ~]# kubectl apply -f pod.yaml 
pod/test created

#查看pod日志
[root@k8s-master ~]# kubectl logs test
0: Sun Sep 29 10:41:49 CST 2024
1: Sun Sep 29 10:41:52 CST 2024
2: Sun Sep 29 10:41:55 CST 2024
3: Sun Sep 29 10:41:58 CST 2024
4: Sun Sep 29 10:42:01 CST 2024

#浏览器中搜索新建pod的日志,如下图。


基于k8s构建EFK+logstash+kafka日志平台_K8s_10