Kubernetes日志收集方案
K8S日志存在的问题
k8s日志收集的难点
在以往的日志收集中,我们可以通过ELK或者EFK架构去收集和管理查看日志,K8S收集日志的难点在于:
- 1.日志类型的多样化,有本地系统日志、标准输出类日志、文件日志,我们要通过怎么样的方式去收集不同种类的日志。
- 2.Pod是动态的,可以根据需求被创建和销毁。Pod的生命周期是短暂的,日志需要在 Pod 消失之前被收集和存储。
- 3.日志的持久化存储问题,如果通过hostpath方式挂载日志文件在本地,我们再从本地收集日志虽然可行,但是pod删除之后挂载的本地目录不会删除,这样就会造成过了一段时间之后磁盘空间占用不断增加,就可以存在打满磁盘的后果。
K8S日志常用方案
1.Node上部署日志收集器
这种方式就是通过在每个Node节点上部署一个Agent来来采集节点级别的日志,该Agent主要负责收集节点上所有容器的日志以及节点上个别应用的日志,比如:/var/lib/docker/containers/目录下的日志
我们通常Daemonset控制器在每个Node节点部署Filebeat之类的日志收集器,来收集节点上的日志文件。
2.Pod附加日志收集的容器
我们可以通过在Pod中增加一个容器专门来收集推送该Pod的容器,比如主要的应用容器通过emptyDir的形式挂载日志目录,然后我们的日志收集器的容器也通通过emptyDir的形式挂载来推送该目录的日志数据到我们的日志收集存储服务(Elasticsearch)中。
这种形式的弊端可能就是会很麻烦,每个服务在编写YAML资源清单的时候都需要手动添加一个容器配置来作为Pod附加的日志收集器。
3.程序直接推送日志
通常有两种做法:
一个就是应用程序通过对应的日志 SDK 进行接入,和应用本身耦合太严重,也不方便后续对接其他的日志系统,所以这种方法一般都不太推荐。
还有就是通过容器运行时提供的 Logging Driver 来实现。以最常用的 Docker 为例,目前已经支持了十多种 Logging Driver。比如你可以配置为fluentd,这个时候 Docker 就会将容器的标准输出日志(stdout、stderr)直接写到fluentd中。
[root@master ~]# cat /etc/docker/daemon.json
{
"registry-mirrors": [ ],
"insecure-registries": ["192.168.101.8"],
"log-driver":"json-file",
"log-opts":{"max-size":"300m", "max-file":"3"}
}
[root@master ~]# docker info |grep 'Logging Driver'
Logging Driver: json-file
不过在K8S中我们会经常使用的kubectl logs就是基于json-flle这种 Logging Driver 来实现的,目前K8S也只支持这一种 Logging Driver。所以在K8S中直接将日志写到后端日志采集系统中去,并不是特别好的做法。
日志收集思路
我们在完成这个方案之前应该思考一下我们需要对哪些类型的日志做出怎样的方案。
- 1.系统各组件的日志,比如apiserver、kubelet、kube-proxy等服务的日志文件。
- 2.标准输出类日志,以容器化方式运行的应用程序自身的日志,比如一些java程序输出的日志
- 3.日志文件类型,以容器化方式运行的应用程序的日志文件,比如nginx等应用。
我们主要针对后面两种日志的收集来指定对应的方案。
标准输出日志
docker容器运行时
我们都知道K8S使用的容器运行时是docker,会把标准输出的日志存放在本地的/var/lib/docker/containers/目录下的每个容器id的目录下。
试想,如果我们能通过容器挂载的方式将节点上的日志目录挂载到容器里面,然后通过日志收集器应用将这些日志推送到我们的日志存储服务(Elasticsearch)中,我们就可以成功地收集标准输出的日志了。
我们进入到这个目录下会发现很多一长串base64一样的字符串目录。如下所示
[root@node01 ~]# cd /var/lib/docker/containers
[root@node01 containers]# ls
......
08b11033a19ebbb94b036c49684c5687e4754931460385740bbeea808cb6670b
08f3d2a36fd16be822f6bbdc34427de8a549cae3ea664060b5cb8310e0b31964
09ad897af6bc4fabea049a06e94996096071b6faed94460cfe487bd2f80867f3
0cdccb6d563366138ddad6d2279a4af56484052e595e230620e4c32c1184d096
0f02ade7f04931304f015b55424b46820179489d7686c80f8da5577a424fa4df
100224ae9ccf06c2e1897c23eaae9c456e81dce5b3ee25af4c958fca85797aad
13bb7ce533ef449a16cb00eaba1206cf075dc35b420ba28022e437cb6e47a72d
14a236e5fcc7eabfc537911a99ebe68afcaf9e1a8168804f80e4f800a29bac23
1594518f5541ac81cf25f3c20e12d61e8f0d89b48c8b0df05ea9cfad823efd56
15c3d1b9a35d2310000cec75f58a6125f1060beb8233bf8b2412c5500b6da647
168779391e0e0111cb85ae5b75169833bbc0d60bb26c3765c10123b13519f028
17945ffbf177d4d200085f2bf1e2bdfbaa89e079a5c5762e4c5b69c464f468e4
......
里面会存放着同样的base64字符串加上-json.log的日志文件,这个就是容器的标准输出存放在本地的日志文件。如下所示:
[root@node01 ~]# ls /var/lib/docker/containers/*/*-json.log
......
/var/lib/docker/containers/0166a07304fd5968bf664e21ad8fbefe65c857b7c5e1a1f288e2b15e9101fbf6/0166a07304fd5968bf664e21ad8fbefe65c857b7c5e1a1f288e2b15e9101fbf6-json.log
/var/lib/docker/containers/037262f82d8192d5b8b999d00710b7fcfd5bd2312a3f4e716c498ea8787754b2/037262f82d8192d5b8b999d00710b7fcfd5bd2312a3f4e716c498ea8787754b2-json.log
/var/lib/docker/containers/08203823a8e57f6994cdf44614f7478a1fc42e9aff6fb3d8b5d86f68a7e5fdd9/08203823a8e57f6994cdf44614f7478a1fc42e9aff6fb3d8b5d86f68a7e5fdd9-json.log
/var/lib/docker/containers/08a09a7e9ba6603c9fa93518f31a61553cddbe5fb67dbe8b4aa35190d6e7462a/08a09a7e9ba6603c9fa93518f31a61553cddbe5fb67dbe8b4aa35190d6e7462a-json.log
/var/lib/docker/containers/08b11033a19ebbb94b036c49684c5687e4754931460385740bbeea808cb6670b/08b11033a19ebbb94b036c49684c5687e4754931460385740bbeea808cb6670b-json.log
/var/lib/docker/containers/08f3d2a36fd16be822f6bbdc34427de8a549cae3ea664060b5cb8310e0b31964/08f3d2a36fd16be822f6bbdc34427de8a549cae3ea664060b5cb8310e0b31964-json.log
......
containerd容器运行时
或者后面的版本默认使用的是containerd,它的日志目录则是/var/lib/containerd/container_logs目录下的每个容器名字加一串uuid类似的字符串为名字的目录下。跟docker容器进行时的思路一样,我们能够找到节点上存放标准输出的日志文件,就能推送到我们的日志存储服务(Elasticsearch)中,然后再进行展示。
我们可以进入到这个目录下面去看会发现他的命名方式以_符号为划分,第一个是命名空间,第二个是容器名称,第三个是uuid的字符串。如下所示:
[root@node02 ~]# cd /var/lib/containerd/container_logs
[root@node02 container_logs]# ls
......
default_test-api-79b977b758-8qs59_75372126-a684-4139-a4a5-3e4f24fad244
default_devops-tool-7bdbf66c47-flp4n_1fa723ef-0be5-44b2-9cc8-4479a7242a55
default_nginx-web-b7dd979-g4v7x_790fe361-23c3-40c1-81b0-bc3eefb7889f
kube-system_cceaddon-nginx-ingress-controller-9fcb4bc98-7fn94_80e06306-33a9-4829-8b10-3c97f25801e7
kube-system_cceaddon-nginx-ingress-default-backend-66d6db855-n64pt_6c64962a-c90e-426f-afbe-48e8eefd105c
kube-system_cceaddon-npd-bsc2d_dae2d1d6-cb10-4163-a23c-0b6b6d75a3fa
kube-system_coredns-68b674b6b8-jxhvx_833890c9-078b-418d-8fea-8fb40cfe76d4
kube-system_everest-csi-controller-8558bbd568-ckv8m_739e9d75-c895-4144-be0c-6d0a0d1fd19c
kube-system_everest-csi-driver-8x798_0b3d9af3-e549-4df4-892c-dfa703947c88
kube-system_icagent-s6dlj_7f4d7719-5b47-46f0-9818-5f802d3bdae9
kube-system_metrics-server-f59678664-85tjr_5f0a1741-234b-47d3-974b-72d28d463c7d
kube-system_node-problem-controller-5c6b5475d4-5stcd_fbb2ee67-12ee-4690-86a4-f297ad3fea85
......
测试日志收集思路
这里我们以docker为容器运行时的K8S集群为实例。
开始测试
通过编写一个job来对日志目录进行挂载,看是不是能够显示里面的日志文件。
编写yaml资源清单如下:
# logs-stdout-test.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: logs-stdout-test-job
spec:
template:
spec:
volumes:
- name: vol-logs
hostPath:
path: /var/lib/docker/containers
containers:
- name: logs
image: busybox:latest
command: [/bin/sh,-c,'ls -l /logs/*/*-json.log']
volumeMounts:
- name: vol-logs
mountPath: /logs
restartPolicy: OnFailure
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
imagePullSecrets:
- name: default-secret
schedulerName: default-scheduler
我们直接apply然后查看日志。
[root@node01 ~]# kubecatl apply -f logs-stdout-test.yaml
job.batch/logs-stdout-test-job created
[root@node01 ~]# kubectl logs logs-stdout-test-job-g2xk4
total 836
......
-rw-r----- 1 root root 0 Jul 4 10:00 /logs/0e9aabf3303e0a1415d8ae3d639ea4a87bfb79490d164182ca7a30921e62718c/0e9aabf3303e0a1415d8ae3d639ea4a87bfb79490d164182ca7a30921e62718c-json.log
-rw-r----- 1 root root 0 Jul 4 06:15 /logs/0f02ade7f04931304f015b55424b46820179489d7686c80f8da5577a424fa4df/0f02ade7f04931304f015b55424b46820179489d7686c80f8da5577a424fa4df-json.log
-rw-r----- 1 root root 108 Jul 4 08:00 /logs/100224ae9ccf06c2e1897c23eaae9c456e81dce5b3ee25af4c958fca85797aad/100224ae9ccf06c2e1897c23eaae9c456e81dce5b3ee25af4c958fca85797aad-json.log
-rw-r----- 1 root root 42882408 Jul 4 10:12 /logs/14a236e5fcc7eabfc537911a99ebe68afcaf9e1a8168804f80e4f800a29bac23/14a236e5fcc7eabfc537911a99ebe68afcaf9e1a8168804f80e4f800a29bac23-json.log
-rw-r----- 1 root root 8441 Jul 4 10:02 /logs/1594518f5541ac81cf25f3c20e12d61e8f0d89b48c8b0df05ea9cfad823efd56/1594518f5541ac81cf25f3c20e12d61e8f0d89b48c8b0df05ea9cfad823efd56-json.log
-rw-r----- 1 root root 0 Jul 4 07:50 /logs/15c3d1b9a35d2310000cec75f58a6125f1060beb8233bf8b2412c5500b6da647/15c3d1b9a35d2310000cec75f58a6125f1060beb8233bf8b2412c5500b6da647-json.log
......
这里我们看到了很多的日志文件,说明这种方法是可行的。
容器中的日志文件
通过节点中收集
在每个K8S的节点上,每个Pod都会在调度到的Node节点上生成一个目录用来存放该Pod的各种文件,其中就有通过emptyDir的形式挂载的文件目录,目录位置就在/var/lib/kubelet/pods/*/volumes/下。
设想,如果真的存在这个目录我们就可以将有日志文件的服务通过emptyDir的形式挂载出日志目录,然后通过每个节点部署日志收集器方式收集节点上的对应通过emptyDir的形式挂载出日志文件的服务的日志数据了。
开始测试
我们开始编写yaml资源清单,如下所示:
# logs-emptydir-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: logs-test-demo
namespace: default
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: logs-test-demo
version: v1
strategy:
type: Recreate
template:
metadata:
labels:
app: logs-test-demo
version: v1
spec:
volumes:
- name: vol-logs
emptyDir: {}
containers:
- name: nginx
image: nginx:latest
volumeMounts:
- name: vol-logs
mountPath: /var/log/nginx
dnsPolicy: ClusterFirst
imagePullSecrets:
- name: default-secret
restartPolicy: Always
schedulerName: default-scheduler
terminationGracePeriodSeconds: 30
然后我们apply创建实例
[root@master ~]# kubecatl apply -f logs-emptydir-test.yaml
deployment.apps/logs-test-demo created
我们到对应的节点上查看该pod的docker容器
[root@node1 ~]# docker ps |grep logs-test-demo
e9f07aeffa8f nginx "/docker-entrypoint.…" Less than a second ago Up Less than a second k8s_nginx_logs-test-demo-6968df9647-8jpl8_default_15f282ac-7e23-4abd-bc79-8c86c471e37a_0
d5bb46020c60 cce-pause:3.1 "/pause" 1 second ago Up 1 second k8s_POD_logs-test-demo-6968df9647-8jpl8_default_15f282ac-7e23-4abd-bc79-8c86c471e37a_0
后面我们可以看到类似uuid的字符串,那个就是本地对应的pod工作目录名,我们进入到对应的pod目录查看一下详情。
[root@node1 ~]# ls /var/lib/kubelet/pods/15f282ac-7e23-4abd-bc79-8c86c471e37a
containers etc-hosts plugins volumes
我们可以看到volumes目录,里面会有一个名为我们YAML资源清单定义的挂载名字的目录,这个目录就是我们通过emptyDir的形式挂载在本地的对应目录,我们可以查看一下是否里面有我们pod容器里面挂载的nginx日志。
[root@node1 ~]# ls /var/lib/kubelet/pods/15f282ac-7e23-4abd-bc79-8c86c471e37a/volumes
kubernetes.io~empty-dir kubernetes.io~projected
[root@node1 ~]# ls /var/lib/kubelet/pods/15f282ac-7e23-4abd-bc79-8c86c471e37a/volumes/kubernetes.io~empty-dir/vol-logs
access.log error.log
这里我们发现了日志文件,说明在平时我们就可以通过收集这里的日志数据来获取Pod容器里面的日志文件。
通过附加日志收集器容器
在上面的常用方案中提到过我们可以Pod附加日志收集器的形式来收集我们每个容器的日志。
那么设想比如无论是docker容器运行时还是containerd容器运行时我们都可以主要的应用容器通过emptyDir的形式挂载日志目录,然后我们的日志收集器的容器也通通过emptyDir的形式挂载来推送该目录的日志数据到我们的日志收集存储服务(Elasticsearch)中。
开始测试
我们开始编写yaml资源清单,如下所示:
# logs-file-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: logs-test-demo
namespace: default
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: logs-test-demo
version: v1
strategy:
type: Recreate
template:
metadata:
labels:
app: logs-test-demo
version: v1
spec:
volumes:
- name: vol-logs
emptyDir: {}
containers:
- name: nginx
image: nginx:latest
volumeMounts:
- name: vol-logs
mountPath: /var/log/nginx
- name: logs
image: busybox:latest
command: [/bin/sh,-c,'tail -f /logs/access.log']
volumeMounts:
- name: vol-logs
mountPath: /logs
dnsPolicy: ClusterFirst
imagePullSecrets:
- name: default-secret
restartPolicy: Always
schedulerName: default-scheduler
terminationGracePeriodSeconds: 30
然后我们apply实例,运行查看logs容器里面的/logs目录下有没有存在nginx的日志文件。
[root@master ~]# kubecatl apply -f logs-file-test.yaml
deployment.apps/logs-test-demo created
[root@master ~]# kubecatl get pods |grep logs-test-demo
logs-test-demo-c4bb85b44-vr5bp 2/2 Running 0 2m7s
[root@master ~]# kubectl exec -it logs-test-demo-c4bb85b44-vr5bp -c logs -- ls -l /logs
total 0
-rw-r--r-- 1 root root 0 Jul 4 10:30 access.log
-rw-r--r-- 1 root root 0 Jul 4 10:30 error.log
我们可以使用curl命令访问一下nginx服务然后再查看日志
[root@master ~]# kubectl exec -it logs-test-demo-c4bb85b44-vr5bp -c nginx -- curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@master ~]# kubectl exec -it logs-test-demo-c4bb85b44-vr5bp -c logs -- cat /logs/access.log
127.0.0.1 - - [04/Jul/2024:10:35:36 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.0" "-"
127.0.0.1 - - [04/Jul/2024:10:35:40 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.0" "-"
说明这个方法也是可行的。
最后总结
通过上面的测试我们发现我们可以通过这种方式来收集我们容器标准输出和容器里面的日志文件。
- 1.容器标准输出的日志通过节点/var/lib/docker/containers日志挂载到日志容器中。
- 2.容器内的日志文件,我们可以通过emptyDir的形式挂载,然后在Node节点上的/var/lib/kubelet/pods目录下收集日志数据。
- 3.容器内的日志文件,我们可以在Pod中增加一个日志收集器,emptyDir的形式挂载,推送到日志存储服务(Elasticsearch)中。
- 4.我们只需要把我们的busybox容器换成filebeat,然后配置一下filebeat容器将日志数据推送到我们的日志存储服务(Elasticsearch)中。
- 5.我们还需要通过Daemonset控制器来对每个节点进行部署收集节点上的日志数据。
这样我们就可以实现在Kubernetes中收集日志功能了。