一、日志级别的简单介绍
日志打印的常见级别:
日志打印通常有四种级别,从高到底分别是: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展示出来。
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的界面上配置,如下图
创建索引
创建时间戳,选择tiemstamp并创建
点击左侧Discover查看日志
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的日志,如下图。