参考文献:
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
3、Node层面
每个容器通过stdout
和stderr
将日志输出到宿主机的日志文件中。
通过在每个节点上
运行一个日志收集的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,将日志文件转发
到后端存储里保存起来。这个方案的架构图如下所示。
不难看到,这里的核心就在于 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上,这样就能够继续使用第一种方案了。这个方案的具体工作原理,如下图所示:
比如,现在我的应用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运行日志代理
上图中,日志代理运行在pod内部,每个pod一个日志代理,而不是每个节点一个日志代理。
如果
节点级
日志代理不够灵活的话,可以创建一个
运行日志代理
的挎斗容器,配置后使它与指定的容器一起工作。
注意
:
灵活性带来开销的增加,并且kubectl logs不再生效,因为日志并非由kubelet负责收集。
作为一个例子,可以使用Stackdriver,它使用fluentd作为日志代理,由下边的两个配置文件实现这种方法。第一个文件是一个ConfigMap用来配置fluentd。
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-config
data:
fluentd.conf: |
type google_cloud
上面的配置文件是配置收集原文件 /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上
6、总结
在本篇文章中,我们详细讲解了Kubernetes项目对容器应用日志的收集方式。
综合对比以上三种方案,个人倾向于使用第一种方式,将应用日志输出到stdout和stderr,然后通过在宿主机上部署logging-agent的方式来集中处理日志。
这种方案不仅管理简单,kubectl logs 也可以用,而且可靠性高,并且宿主机本身,很可能就自带了rsyslogd等非常成熟的日志收集组件来供你使用。
最后需要指出的是,无论是哪种方案,你都必须要及时将这些日志文件从宿主机上清理掉,或者给日志目录专门挂载一些容量巨大的远程盘。否则,一旦主磁盘分区被打满,整个系统就可能会陷入奔溃状态,这是非常麻烦的。