我们说一下,K8S中的日志管理
我们讲解了Kubernetes的核心监控体系和自定义监控体系的设计和实现思路,在本篇中,我们讲解一下Kubernetes中关于容器日志的处理方式
Kubernetes中关于容器日志的处理方式,统一的称为cluster-level-logging,这个日志处理系统,和容器,Pod以及Node的生命周期都是无缘的,这样,无论是容器挂了,还是节点宕机的时候,都可以正常的获取到日志的信息
默认情况下,当应用进行日志的输出的时候,是输出到了stdout和stderr的,这样容器项目会在默认的时候将这些日志输出到宿主机上的一个JSON文件中,这样,通过kubectl logs的命令就可以看到这些容器的日志
而Kubernetes本身,是不会为其做容器日志收集工作的,为了实现上述的cluster-level-logging,需要在部署集群的时候,提前对具体的日志方案进行规划,常见的方案包含三种
第一种,在Node上部署logging agent,将日志文件转发到后端存储里保存起来,这个方案的架构如下
这里的核心,就在于logging agent,会以DaemonSet的方式运行在节点上,就将宿主机上的容器日志挂载进去,由logging-agent将日志转发出去
常见的logging-agent有Fluentd项目,其可以将日志转发到远端的ElasticSearch里保存起来供其将来进行检索
这样部署的话,就可以在一个节点上只部署一个agent,而且对应用和Pod没有任何的侵入性,所以,这个方案是社区中常见的方案.
但是有一定的缺陷,就是这个方案要求应用输出的日志,必须都要直接输出到容器的stdout和stderr中,所以有着Kubernetes容器日志方案的第二种,就是对于这种特殊情况的一种处理,当容器的日志只能输出到某些文件中的时候,我们可以通过一个sidecar容器将这些日志文件重新输出到sidecar的stdout和stderr了,这样就能够使用第一种来进行远端的保存了,这种方案的工作原理如下
具体的操作如下,我们的应用Pod只有一个容器,就会将日志输出到容器的/var/log/1.log和2.log这两个文件中,这个Pod的YAML文件如下所示
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 volumes: – name: varlog emptyDir: {} |
这种情况下,因为没有输出到容器原本的log文件中,导致kubectl logs是看不到应用的任何日志的,我们定义的常见的方案,也是没法使用的,这时候,我们就可以为这个Pod添加两个sidecar容器,分别将上述的两个日志文件的内容重新以stdout和stderr的方式输出
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-log-1 image: busybox args: [/bin/sh, -c, ‘tail -n+1 -f /var/log/1.log’] volumeMounts: – name: varlog mountPath: /var/log – name: count-log-2 image: busybox args: [/bin/sh, -c, ‘tail -n+1 -f /var/log/2.log’] volumeMounts: – name: varlog mountPath: /var/log volumes: – name: varlog emptyDir: {} |
这时候,这两个sidecat容器就可以以kubeclt logs的方式进行查看了,间接的看到应用的日志内容.
而且,我们新开启的sidecar容器和主容器也是共享Volume的,所以这里的sidecar方案的额外性能消耗并不高,只是多占用一些CPU和内存罢了
但是,唯一的问题在于,宿主机上会存在两个相同的日志文件,一份是应用自己写的,一份是sidecar的stdout和stderr的JSON文件,这对磁盘是很大的浪费,所以不到万不得已,还是不选择方案二了.
那就是第三种,就是通过一个sidecar容器,将应用的日志文件发送给远程存储里面去.相当于将方案一中的logging agent,放到了应用Pod里,这种方案的架构如下
我们直接将日志输出到固定的文件而不是一个stdout,而交由一个logging-agent进行相关的管理,也可以提交给后端的ElasticSearch,只不过fluentd的输入源,变为了应用的日志文件,我们将fluentd的输入源配置保存在一个ConfigMap中
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> |
我们应用Pod的定义中,声明了一个Fluentd容器作为sidecar,将生成的对应的1.log 2.log转发给ElasticSearch中,配置如下
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 |
这样,这个Fluentd容器的输入源,就是通过引用我们的ConfigMap来指定的,我们用了Projected Volume来将ConfigMap挂载到Pod中
这种方式部署简单,对宿主机相对友好,但是每个Pod都有一个sidecar容器,都会消耗一些额外的资源,甚至拖垮应用容器,而且,是直接转发出去了,kubectl logs还是看不到日志输出的
这就是Kubernetes项目对容器应用日志进行管理的常用的三种手段的
总结一下
我们讲解了下Kubernetes项目对容器应用日志的收集方式,综合对比了三种方案,其实还是最原始的,将logging-agent部署在宿主机上,应用日志输出到stdout和stderr.
这种方案管理简单,kubectl logs可以直接使用,还可以输出到其他的存储后端上
或者在编写应用的时候,直接配置一个日志的存储后端,如下所示
这样,Kubernetes就不用操心日志的收集,收集已经是完善的了
但是,无论何种日志存储方案,都需要及时的将日志文件从宿主机上清理掉,或者给日志目录专门挂载一些容器巨大的存储盘,避免磁盘爆满
那么日志量大的时候,直接将日志输出到stdout和stderr上,会有隐患吗?
还有什么容器收集的方案吗?