kubernetes 日志采集架构方案

参考文献:
https://blog.csdn.net/dkfajsldfsdfsd/article/details/80970179

日志方案的关注点

  • 日志来源和存放位置
  • 日志采集上报
  • 日志存储
  • 日志分析
  • 日志查询

日志主要分成两种,

  • 一种是应用日志
  • 一种是系统日志

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

1、介绍

关于kubernetes的日志分几种,针对kubernetes本身而言有三种:

  • 资源运行时的event事件。比如在k8s集群中创建pod之后,可以通过kubectl describe pod 命令查看pod的详细信息。
  • 容器中运行的应用程序自身产生的日志,比如tomcat、nginx、php的运行日志。
  • k8s各组件服务日志,比如systemctl status kubelet。

容器日志收集的方式通常有以下几种:

  • 宿主机Node层面统一收集:将宿主机的目录挂载为容器的日志目录,然后在宿主机上收集。

  • pod中以sidecar容器的方式收集:这个sidecar容器收集应用日志之后,可以输出到宿主机或者直接发送到远端的日志收集服务。

  • 网络收集: 容器内应用将日志直接发送到日志中心,比如java程序可以使用log4j2转换日志格式并发送到远端。或者通过修改docker的–log-driver。可以利用不同的driver把日志输出到不同地方,将log-driver设置为syslog、fluentd、splunk等日志收集服务,然后发送到远端

2、kubernetes中标准输出中的输出日志

通过一个例子了解kubernetes中标准输出中的输出日志。容器中执行shell, 定时输出统计数。
debug/counter-pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: 192.168.200.197:80/test-private/busybox:latest
    args: [/bin/sh, -c,
            'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']

通过下面的方式运行:

kubectl apply -f https://k8s.io/examples/debug/counter-pod.yaml
pod/counter created

查看日志方式:

[root@kube-196 ~]# kubectl logs -f counter
1126: Tue Jun 18 09:27:07 UTC 2019
1127: Tue Jun 18 09:27:08 UTC 2019
1128: Tue Jun 18 09:27:09 UTC 2019
1129: Tue Jun 18 09:27:10 UTC 2019
1130: Tue Jun 18 09:27:11 UTC 2019
1131: Tue Jun 18 09:27:12 UTC 2019

这些日志文件,在宿主机上的什么位置呢?

[root@kube-196 ~]# less /var/log/pods/default_counter_9ce0c974-91a8-11e9-9cd8-fa163e1e5fde/count/0.log
{"log":"1081: Tue Jun 18 09:26:22 UTC 2019\n","stream":"stdout","time":"2019-06-18T09:26:22.866910994Z"}
{"log":"1082: Tue Jun 18 09:26:23 UTC 2019\n","stream":"stdout","time":"2019-06-18T09:26:23.868754044Z"}
{"log":"1083: Tue Jun 18 09:26:24 UTC 2019\n","stream":"stdout","time":"2019-06-18T09:26:24.870266589Z"}
{"log":"1084: Tue Jun 18 09:26:25 UTC 2019\n","stream":"stdout","time":"2019-06-18T09:26:25.871929216Z"}

[root@kube-197 kube-proxy]# cd /var/log/containers/
[root@kube-197 containers]# ll
total 112
lrwxrwxrwx 1 root root 101 Jun 25 10:47 apm-els-elasticsearch-master-2_default_chown-2a0eea1ac1a4c4a8fe66fa5ab16bbdcf896fde9c5e752abdc5bbe6340fd00a17.log -> /var/log/pods/default_apm-els-elasticsearch-master-2_ac94ba63-96f2-11e9-9cd8-fa163e1e5fde/chown/0.log
lrwxrwxrwx 1 root root 109 Jun 25 10:47 apm-els-elasticsearch-master-2_default_elasticsearch-e6727337b8d1146868f480ce829a13100629aa78f61786c1aa8823850bfdfbc5.log -> /var/log/pods/default_apm-els-elasticsearch-master-2_ac94ba63-96f2-11e9-9cd8-fa163e1e5fde/elasticsearch/0.log

image

3、Node层面

image

每个容器通过stdoutstderr将日志输出到宿主机的日志文件中。

通过在每个节点上运行一个日志收集的agent来采集日志数据,日志采集agent是一种专用工具,用于将日志数据推送到统一的后端。
一般来说,这种agent用一个容器来运行,可以访问该节点上所有应用程序容器的日志文件所在目录。
由于这种agent必须在每个节点上运行,所以直接使用DaemonSet控制器运行该应用程序即可。

在节点上运行一个日志收集的agent这种方式是最常见的一直方法,因为它只需要在每个节点上运行一个代理程序,并不需要对节点上运行的应用程序进行更改,对应用程序没有任何侵入性,但是这种方法也仅仅适用于收集输出到stdout 和 stderr 应用程序日志`。

Note: Docker json日志驱动将每一行当做一条独立的
消息。如果想支持多行消息,需要在agent或者更高的层面去自行处理。

Note: 如果使用了logrotate,那么只能获取最新的文件,比如有个15MB的文件,通过logrotate分割成一个10MB和一个5MB的文件,那么kubectl logs只能查看5MB的文件。

4、系统组件日志

系统组件有两种类型:

  • 容器中运行的系统组件
  • 不在容器中运行的系统组件。
    例如:
    Kubernetes的scheduler和kube-proxy在容器中运行。
    kubelet和容器运行时(例如Docker)不在容器中运行。
    对于系统级的日志文件,也是存放在/var/log目录下,也需要配置logrotate分割。

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

5、集群级日志采集架构方案

首先需要明确的是,Kubernetes里面对容器日志的处理方式,都叫作 cluster-level-logging,即:这是个日志处理系统,与容器、Pod以及Node的生命周期都是完全无关的。这种设计当然是为了保证,无论是容器挂了、Pod被删除,甚至节点宕机的时候,应用的日志依然可以被正常获取到。
而对于一个容器来说,当应用把日志输出到 stdout 和 stderr 之后,容器引擎在默认情况下就会把这些日志输出到宿主机上的一个 JSON 文件里。
这样,你通过 kubectl logs 命令就可以看到这些容器的日志了。
上述机制,就是我们今天要讲解的容器日志收集的基础假设。
而如果你的应用是把文件输出到其他地方,比如直接输出到了容器里的某个文件里,或者输出到了远程存储里,那就属于特殊情况了。
当然,我在文章里也会对这些特殊情况的处理方法进行讲述。
而 Kubernetes 本身,实际上是不会为你做容器日志收集工作的,所以为了实现上述cluster-level-logging,你需要在部署集群的时候,提前对具体的日志方案进行规划。而 Kubernetes 项目本身,主要为你推荐了三种日志方案。

  • 每个node部署一个node级别的代理

  • 每个应用的pod中集成一个sidecar容器

  • 从应用内部直接推送日志到后端

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

5.1、每个node部署一个node级别的代理

第一种,在Node上部署logging agent,将日志文件转发到后端存储里保存起来。这个方案的架构图如下所示。

image

不难看到,这里的核心就在于 logging agent ,它一般都会以 DaemonSet 的方式运行在节点上,然后将宿主机上的容器日志目录挂载进去,最后由 logging-agent 把日志转发出去。

举个例子,我们可以通过 Fluentd 项目作为宿主机上的 logging-agent,然后把日志转发到远端的 ElasticSearch 里保存起来供将来进行检索。

因为k8s集群已经将pod日志在集群的/var/log/pods/xxxxxx/xxx/xxx.log 目录下做了软连接(xxxx.log)到pod所在 /var/lib/docker/containers/xxxxx/-json.log 文件中,只需要将fluentd监控相关/var/log/pods/ 目录下的.log 文件,即可达到搜集日志的效果,同时由于在 docker 层设置了日志分割方式,将日志占用空间做了限制,基本上不会出现日志过期不能清理的问题。

 "log-opts": {
                "max-size": "100m"
        },

另外,在很多 Kubernetes 的部署里,会自动为你启用 logrotate,在日志文件超过10MB的时候自动对日志文件进行 rotate 操作。
可以看到,在Node上部署logging agent 最大的优点,在于一个节点只需要部署一个agent,并且不会对应用和 Pod有任何侵入性。
所以,这个方案,在社区里是最常用的一种。
但是也不难看到,这种方案的不足之处就在于,它要求应用输出的日志,都必须是直接输出到容器的stdout和stderr 里。
具体参考官网的方案配置:https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/fluentd-elasticsearch

5.2、每个应用的pod中集成一个sidecar容器

使用sidecar容器方式,有两种方式:

  • sidecar将应用日志输出到stdout
  • sidecar运行日志代理

5.2.1、sidecar将应用日志输出到stdout

所以,Kubernetes容器日志方案的第二种,就是对这种特殊情况的一个处理,
即:当容器的日志只能输出到某些文件里的时候,我们可以通过一个sidecar容器把这些日志文件重新输出到 sidecar的stdout和stderr上,这样就能够继续使用第一种方案了。这个方案的具体工作原理,如下图所示:

image

比如,现在我的应用Pod只有一个容器,它会把日志输出到容器里的 /var/log/1.log 和 2.log 这两个文件里。
这个 Pod 的 YAML 文件如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: 192.168.200.197:80/test-private/busybox:latest
    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: {}

在这种情况下,你用kubectl logs 命令是看不到
应用的任何日志的;/var/log/pods也没有日志文件。而且我们前面讲解的、最常用的方案一,也是没办法使用的。
那么这个时候,我们就可以为这个 Pod 添加两个sidecar 容器,分别将上述两个日志文件里的内容重新以 stdout 和 stderr 的方式输出出来,这个 YAML 文件的写法如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: 192.168.200.197:80/test-private/busybox:latest
    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: 192.168.200.197:80/test-private/busybox:latest
    args: [/bin/sh, -c, 'tail -n+1 -f /var/log/1.log']
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-log-2
    image: 192.168.200.197:80/test-private/busybox:latest
    args: [/bin/sh, -c, 'tail -n+1 -f /var/log/2.log']
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  volumes:
  - name: varlog
    emptyDir: {}

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

$ 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
...

由于 sidecar 跟主容器之间是共享Volume的,所以这里的sidecar方案的额外性能损耗并不高,也就是多占用一点 CPU 和内存罢了。

注意:这种方式宿主机上实际上会存在两份相同的日志文件:
一份是应用自己写入的;
另一份则是sidecar 的stdout和stderr对应的JSON文件。
这对磁盘是很大的浪费。所以说,除非万不得已或者应用容器完全不可能被修改,

5.2.2、sidecar运行日志代理

image

上图中,日志代理运行在pod内部,每个pod一个日志代理,而不是每个节点一个日志代理。
如果节点级日志代理不够灵活的话,可以创建一个运行日志代理的挎斗容器,配置后使它与指定的容器一起工作。
注意
灵活性带来开销的增加,并且kubectl logs不再生效,因为日志并非由kubelet负责收集。
作为一个例子,可以使用Stackdriver,它使用fluentd作为日志代理,由下边的两个配置文件实现这种方法。第一个文件是一个ConfigMap用来配置fluentd。

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>

上面的配置文件是配置收集原文件 /var/log/1.log 和 /var/log/2.log 的日志数据,然后通过 google_cloud 这个插件将数据推送到 Stackdriver 后端去。
下面是我们使用上面的配置文件在应用程序中运行一个 fluentd 的容器来读取日志数据:

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: 192.168.200.197:80/test-private/busybox:latest
    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

上面的Pod创建完成后,count-agent容器就会将 count 容器中的日志进行收集然后上传。当然,这只是一个简单的示例,我们也完全可以使用其他的任何日志采集工具来替换 fluentd,比如 logstash、fluent-bit 等等。

5.2.3、从应用内部直接推送日志到后端

除了容器应用的日志之外,还有一个重要的部分就是,程序自身的日志。
一般情况下,会将程序日志写到固定目录文件、或者写入到中间件保存和读取。

这部分日志的处理,可以直接通过日志文件收集、或者是让开发程序的同学修改程序把日志输出到中间件或者是直接输出到elk上


image

6、总结

在本篇文章中,我们详细讲解了Kubernetes项目对容器应用日志的收集方式。
综合对比以上三种方案,个人倾向于使用第一种方式,将应用日志输出到stdout和stderr,然后通过在宿主机上部署logging-agent的方式来集中处理日志。
这种方案不仅管理简单,kubectl logs 也可以用,而且可靠性高,并且宿主机本身,很可能就自带了rsyslogd等非常成熟的日志收集组件来供你使用。

最后需要指出的是,无论是哪种方案,你都必须要及时将这些日志文件从宿主机上清理掉,或者给日志目录专门挂载一些容量巨大的远程盘。否则,一旦主磁盘分区被打满,整个系统就可能会陷入奔溃状态,这是非常麻烦的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容