暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

K8S之容器日志管理收集方案和实践

CloudNativeX 2021-06-19
1697

        点击上方👆蓝字关注!

这里我们开门见山,K8S里面对容器日志的处理方式,都可以叫作日志处理系统,它与容器、pod 以及 node 的生命周期都完全没有关系。通过这种设计,是为保证无论是容器挂了、pod 被删除,还是节点宕机的情况下,应用日志可以保持正常的被获取到。

而且,对于一个容器来说,当应用把日志输出到 stdout 和 stderr 之后,项目的容器在默认情况下,会把这些日志输出到宿主机上的一个json文件里。这样,我们可以通过kubectl logs的方式,可以直接看到这些容器的运行日志。

以上这种方式,正是我这次分享,要讲解的容器日志收集的基础理论。但,假如你的应用日志是把文件存储到其他地方,比如输出到了容器里的某个文件,或输出到远程存储里系统上,就只能利用别的方式了。

对于K8S,它实际不是会为了让你做容器日志收集方面的工作,所以为了实现上面的日志处理系统,我们在部署集群时,需要对应用的日志系统进行规划。对于运行在K8S上的项目,可以为你推荐以下三种日志收集方案。

第一,在node上部署 日志收集的agent,然后将应用产生的日志文件,转发到后端存储里保存起来。这种方式,我这里给出的架构图如下:

我们可以看到,这个架构的基础,在于日志收集工具的agent ,而这种方案一般都是以daemonset 的方式,运行在集群的各个node工作节点上,然后将宿主机上,容器产生的日志目录挂载进去,最后由agent把应用的日志转发再进行存储。

有个栗子,假如我们通过fluentd工具作为我们宿主机上的日志系统的agent,通过fluentd把日志转发到远程的es进行保存下来,以备后续我们进行日志的查询检索。而且,在很多K8S的部署方式上,都会自动启用logrotate,如果日志文件超过10mb时,它自动对日志文件进行rotate的操作。

这里可以知道,在node节点上部署日志的agent比如fluentd,它最大的优势是在一个节点上,只要部署一个收集日志的agent 客户端,这种方式不会对应用本身和pod有任何影响,达到完全解耦的目的。最后,这种日志收集的解决方案,也是目前很多公司进行日志收集最常用的一种方式。

当然,每个事务都有正反面,这种方式当然也不例外,它要求应用产生的日志,在进行输出时,必须是直接输出到容器的stdout 和 stderr里面。

第二种方案,就是对这种特殊情况的一个处理,即:当容器的日志只能输出到某些文件,这时就可以通过sidecar容器的方式,将这些日志文件重新输出到 sidecar的stdout和stderr上面,这样又能使用第一种通过agent收集日志的方案了。这种解决方案的架构如下图所示:

比如,现在这里有一个应用pod它只有一个容器,它会把日志输出到容器里的 /data/log/test01.log 和 /data/log/test02.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)" >> /data/log/test01.log;
        echo "$(date) INFO $i" >> /data/log/test02.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
      mountPath: /data/log/
volumes:
- name: varlog
    emptyDir: {}

通过这种方式,如果我们使用kubectl logs进行日志的查看,是看不到应用的的任何日志。这种情况,我们就需要为这个应用 pod 添加两个sidecar容器,就是分别将上述两个日志文件里的内容,重新通过stdout 和 stderr 的方式输出,然后这个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)" >> /data/log/test01.log;
        echo "$(date) INFO $i" >> /data/log/test02.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
    - name: testlog
      mountPath: /data/log/
  - name: test01-log-1
image: busybox
    args: [/bin/sh, -c, 'tail -n+1 -f /data/log/test01.log']
volumeMounts:
- name: testlog
      mountPath: /data/log/
  - name: test02-log-2
image: busybox
    args: [/bin/sh, -c, 'tail -n+1 -f /data/log/test02.log']
volumeMounts:
- name: varlog
mountPath: /data/log/
volumes:
- name: varlog
emptyDir: {}

这时候,你就可以通过 kubectl logs 命令查看这两个 sidecar 容器的日志,间接看到应用的日志内容了,如下所示:

$ kubectl logs counter test01-log-1
0: Mon Jan 1 00:00:00 UTC 2001
1: Mon Jan 1 00:00:01 UTC 2001
2: Mon Jan 1 00:00:02 UTC 2001
...


$ kubectl logs counter test02-log-2
Mon Jan 1 00:00:00 UTC 2001 INFO 0
Mon Jan 1 00:00:01 UTC 2001 INFO 1
Mon Jan 1 00:00:02 UTC 2001 INFO 2

这样sidecar它跟主容器之间是共享volume卷的,所以这里的 sidecar 方案的对于额外的性能损耗不会太多,也就会多占用一点 CPU 和内存了。

但同时我们需要注意的是,这时候,宿主机上实际上会存在两份相同的日志文件:一份是应用本身产生自己写入的;而另一份是sidecar的stdout和stderr 对应的json文件。所以这对磁盘资源,有一些浪费。所以说,除非万不得已或者应用容器完全不可能被修改,否则我还是建议你直接使用方案一,或者直接使用下面的第三种方案。

第三种方案,就是通过一个 sidecar 容器,直接把应用的日志文件发送到远程存储里面去。也就是相当于把方案一里的logging agent,放在了应用 Pod 里。这种方式的架构如下:

在这种方案中,应用还可以直接把日志输出到固定的文件里而不是 stdout,你的 logging-agent 还可以使用 fluentd,后端存储还可以是ES。对应的, fluentd 的输入源,变成了应用的日志文件。一般来说,我们会把 fluentd 的输入源配置保存在一个configmap 里,如下所示:

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

然后,我们在应用 Pod 的定义里,就可以声明一个fluentd的容器作为 sidecar,它专门负责将应用生成的 test01.log 和 test02.log转发到ES存储当中,这个配置我们将它表示为如下所示:

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)" >> /data/log/test01.log;
        echo "$(date) INFO $i" >> /data/log/test02.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
      mountPath: /data/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: /data/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 里。

然后这里我们要注意的是,这种解决方案虽然部署比较简单,并且对宿主机非常友好,但是这个sidecar 容器很可能会消耗较多的资源,甚至拖垮应用容器。并且,由于日志还是没有输出到 stdout 上,所以你通过kubectl logs也是看不到任何日志输出的。

以上的三种方案,就是K8S项目对容器应用日志进行管理最常用的三种方式,这里,我还是比较推荐第一种。

当然,基于像最新的Loki日志方案,前面的文章已经罗列和给大家演示过,篇幅有限,这里就不再重复,感兴趣,可以去翻翻前面的文章。

总结

这篇文章,比较详细给你普及了关于K8S项目对于容器应用日志的收集方式。通过对比以上三种解决方案,我比较建议的是,将应用日志输出到 stdout 和 stderr,然后通过在宿主机上部署 logging-agent 的方式,来集中式的处理日志。

这种架构不仅在管理上简单,kubectl logs命令查询也可以使用,而且可靠性高,并且宿主机本身,很可能就自带了 rsyslogd 等非常成熟的日志收集组件来供你使用。

除此之外,还有一种方式就是在编写应用的时候,就直接指定好日志的存储后端,如下所示:

在这种方案下,Kubernetes 就完全不必操心容器日志的收集了,这对于本身已经有完善的日志处理系统的公司来说,是一个非常好的选择。

👇👇👇点击关注,更多内容持续更新...

               


文章转载自CloudNativeX,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论