kubernetes日志架构

日志主要分成两种,一种是应用日志,另外一种是系统日。日志对于理解集群内部的工作原理非常有帮助,另外日志对于调试、排错、监控集群活动特别有用。大部分的现在应用都支持某种类型的日志机制,相似的,大部分的容器引擎也被设计成支持此种类型的日志机制。标准输出及标准错误输出是最简单的也是大部容器内嵌的日志机制。
然而,容器引擎支持的原生日志系统往往不是有关日志的完全解决方案。例如,容器销毁、pod驱除、节点死亡等,发生这种情况后,用户往往仍然需要查看日志,但是日志此时已经伴随容器、pod、节点被同步销毁。因些,日志应该有独立于节点、pod、容器的存储空间及生命周期,这一概念称之为集群级日志(cluster-level-logging)。集群级日志要求一个独立的后端存储、分析、查询日志。kubernetes没有为日志数据的存储提供原生的解决方案,但是用户可以很容易将已存在的日志解决方案集成到kubernetes集群中。
集群级日志假定用户有一个位于集群内部或者外部,专门用于处理日志的后端。如果对集群级日志不感兴趣,仍然能够从本文中学到有关日志的存储、处理等非常有用的知识点。

kubernetes基本日志处理

本节展示一个kubernetes的基本日志处理,即将日志输出到标准输出流。本例运行包含一个容器的pod,容器以每秒种一次的速度将文本日志输出到标准输出。

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)"; i=$((i+1)); sleep 1; done']

用如下命令创建运行pod

$ kubectl create -f https://k8s.io/examples/debug/counter-pod.yaml
pod "counter" created
$ kubectl logs counter
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
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
...

可以在运行此命令时再加上标志--previous flag回看上一个已经销毁实例的日志,同时在命令中应该指定容器的名称以便于说明想查看的是那个容器的日志。需要注意的是,在被明确删除之前,即使是销毁的容器也依然存在于系统中,所以才能回看与它生命周期相同的日志。

节点级日志

容器写到stdout与stderr的所有内容,会被系统定义好的日志引擎处理。默认情况下,docker容器引擎将这两个流以json的格式重定向到节点的某个目录下。

注意:docker的json日志引擎会将原始日志中的每一行当成一条记录,docker的这种日志引擎没有对一条日志由多行组成这种情况的直接支持,用户需要在日志代理层或者更高的层次上特别处理这种情况。

默认的行为,如果容器被停止,kubelet将会在系统中保留被停止的容器及其日志。但是如果pod被从当前节点排除出去,那么与pod对应的所有容器及其日志均被删除。

节点级日志的一个重要量虑是实现日志滚动,以免因为保存日志消耗掉所有可用的存储空间。目前,kubernetes本身并不负责日志的滚动,便是可以通过部署工具的设置解决这个问题。例如,在通过kube-up.sh部署的kubernetes集群中,有一个每小时滚动日志的工具,或者是通过设置容器运行时环境自动滚动日志,例如通过设置docker守护进程的与日志有关的选项。默认设置下,当日志的大小超过10MB时滚动。这种情况属于默认行为,日志与容器本身共享存储空间,两者生命周期也一致。

当运行kubectl logs命令时,节点上的kubelet响应命令,直接从日志文件中读取日志并应答。

注意:目前,如果外部系统滚动了日志,仅有最新的日志文件可以通过kubectl logs查看。例如,如果有一个10MB的文件,被滚动成了两个文件,一个10MB,另一个是空文件,则kubectl logs命令会返回空。

系统组件日志

系统组成分成两种,一种以容器方式运行,一种以非容器方式运行:

  • The Kubernetes scheduler and kube-proxy run in a container.
  • The kubelet and container runtime, for example Docker, do not run in containers.

对于具备systemd功能的主机,kubelet与容器运行时将日志写入到journald中,否则,日志写入到/var/log目录下的.log文件中。对于运行在容器内部的系统组件,通过使用glog日志处理库,将日志写入到/var/log目录下,绕开默认的日志处理机制。总之,系统组件的日志处理机制独立于容器处理机制。对于以systemd服务等式运行的组件,日志由journald处理。以容器方式运行的组件,日志最终到输出到宿主机的/var/log目录下,这种情况下需要手动处理日志的滚动,大多数情况通过安装脚本自动完成。

集群级日志架构

kubernetes没有提供原生的集群级日志解决方案,以下几个方案可供选择:

  • 在每个节点上运行节点级日志代理
  • 在每个pod中包含一个专门用于处理日志的挎斗容器。
  • 在容器中直接推送日志到负责处理日志的后端。

以上三种方法,可以总结成节点级、pod级、容器级集群级日志架构解决方案。

使用节点日志代理

从上图可知,可以在每个节点上运行一个节点级日志代理,由它负责将日志转发到后端实现实现集群级日志架构。一般来说,节点上的所有容器将日志写入宿主机上的某个目录,由宿主机上的某个工具负责日志的流动,节点级日志代理是一个能够访问这个目录的一个容器,再由它负责将所有的日志推送到日志后端。
节点级代理是推荐的实现集群级日志架构的方法,因为这种方法每个节点只需要创建一个代理,并且不需要修改节点上运行的容器。但是节点级日志代理只对标准输出及标准错误输出有效。
节点日志需要在每个节点上运行,因此它一般是通过DaemonSet replica、a manifest pod实现,也可以是一个专用的普通程序,重点是一个节点一个代理,当然推荐以及大部分情况下都使用第一种方法。

kubernetes本身没有特别指定用什么样的节点级日志代理,但是在kubernetes的发行版中包含了两种节点级日志代理的实现:谷歌云平台使用的Stackdriver与Elasticsearch,详细的配置、使用方法参考专业文档。两者都通过配置fluentd(数据收集)作为节点上的代理。

使用挎斗容器作为日志代理

可以通过以下两种方法使用挎斗容器:

  • 挎斗容器将所有应用的日志重定向到自己的stdout
  • 挎斗容器运行一个日志代理并将应用的日志检出

流挎斗容器:

上图与节点级日志代理的区别只有一处,在my-mod中多了一个streamin container,相当于是多了一级处理。这个容器可以从位于相同pod中的其它容器任意位置收集日志,比如文件、socker、 journald而不仅仅是stdout、stderr。这样的话,其它容器就可以将日志先输出到文件,然后再由streamin container集中收集并输出到自己的stdout、stderr,相比于每个容器都向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
  volumes:
  - name: varlog
    emptyDir: {}

为它加装两个流挎斗:

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: {}

运行如下命令分别获取两种不同的日志:

$ kubectl logs counter count-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 count-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
...
如此一来,每个节点上的节点级日志代理无需作任何修改,就可以从stream container收集原来输入到两个文件中去的两种不同格式的日志。如果有必要,可以将stream container修改的更复杂一些,如对log作一些格式上的处理等。
注意,这种方法尽管CPU内存开销很小,但是因为首先要将日志写入文件,然后再从文件中读出输出到标准输出,因此会产双倍的硬盘读写开销。如果容器中运行的应用只将日志写入单一的文件,更好的选择是将输出重定向到/dev/stdout而不是加装流挎斗。
关于日志滚动,在本例中是容器内部实现的,也可以在stream container中实现。但是,推荐的方式始终是将日志直接输出到标准输出,滚动交由kubelet负责。

日志代理挎斗容器:

上图中,日志代理运行在pod内部,每个pod一个日志代理,而不是每个节点一个日志代理。

如果节点级日志代理不够灵活的话,可以创建一个运行日志代理的挎斗容器,配置后使它与指定的容器一起工作。
注意:灵活性带来开销的增加,并且kubectl logs不再生效,因为日志并非由kubelet负责收集。

作为一个例子,可以使用Stackdriver,它使用fluentd作为日志代理,由下边的两个配置文件实现这种方法。第一个文件是一个ConfigMap用来配置fluentd。

apiVersion: v1
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>
kind: ConfigMap
metadata:
  name: fluentd-config

fluentd配置参考文档:http://docs.fluentd.org/

下面的文件描述一个pod,它的挎斗容器运行fluentd应用,pod挂载了一个volume,里边包含fluentd的配置项。

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
过一些时间应该可以从Stackdriver接口上看到日志。

记住,这只是一个例子,可以用任何喜欢的日志代理替换掉fluentd,也可以从容器的任意位置收集日志。

将日志直接从应该推送到后端

其实现方式就是在应用中将日志直接推送到后端,不走通常意义上的日志处理机制。每个应用都需要单独处理日志,每个应用都需负责与日志后端的交互。其实现在麻烦,效率也应该最高。


总结:集群中的普通应用,推荐使用流挎斗容器。对于有特别需求的应用,如日志后端不同、需要对日志作特殊处理等使用日志代理挎斗容器。

原文:https://kubernetes.io/docs/concepts/cluster-administration/logging/

猜你喜欢

转载自blog.csdn.net/dkfajsldfsdfsd/article/details/80970179