1.从主机到容器
-
动态迁移 : 在 Kubernetes 集群中经常存在 Pod 主动或者被动的迁移,频繁的销毁、创建,我们无法和传统的方式一样人为的给每个服务下发日志采集配置。
-
日志存储方式多样性 : 容器的日志存储方式有很多不同的类型,例如 stdout、hostPath、emptyDir、pv 等。
-
Kubernetes 元信息 : 由于日志数据采集后会被集中存储,所以查询日志时,需要根据namespace、pod、container、node,甚至包括容器的环境变量、label 等维度来检索、过滤,此时要求Agent感知并默认在日志里注入这些元信息。
以上都是有别于传统日志采集配置方式的需求和痛点,究其原因,还是因为传统的方式脱离了Kubernetes,无法感知Kubernetes,无法和Kubernetes集成。
为了采集容器日志,我们先来看一下市面上一般都有哪些解决方案。
2.1 采集的日志类型
-
需要业务方修改日志配置,比较难以推广 -
有些复杂的业务对日志文件有分类,比如审计日志、访问日志等,一般会输出为独立的日志文件,日志采集需根据不同的文件分类进行不同的处理
2.2 Agent部署方式
采集容器日志,Agent有两种部署方式:
-
DaemonSet: 每个节点部署一个Agent
-
Sidecar: 每个Pod增加一个Sidecar容器,运行日志Agent
两种部署方式的优劣都显而易见:
-
资源占用: DaemonSet每个节点上一个,而Sidecar每个Pod里一个,容器化形态下,往往一个Node上可能会跑很多的Pod,此时DaemonSet的方式远小于Sidecar,而且节点上Pod个数越多越明显 -
侵入性: Sidecar的方式,Agent需要注入到业务Pod中,不管是否有平台封装这一过程,还是采用Kubernetes webhook的方式默认注入,仍然改变了原本的部署方式 -
稳定性: 日志采集在大部分的情况下,需要保障的是稳定性,最重要的是不能影响业务,如果采用Sidecar的方式,在Agent发生异常或者oom等情况,很容易对业务容器造成影响。另外,Agent比较多的时候,在连接数等方面会对下游服务比如Kafka造成一定的隐患。 -
隔离性: DaemonSet情况下,节点所有的日志都共用同一个Agent,而Sidecar方式,只会采集同一个Pod内的业务日志,此时Sidecar的隔离性理论上会好一些 -
性能: Sidecar由于只会采集该Pod里的日志,压力相对较小,极端情况下,达到Agent的性能瓶颈比DaemonSet方式概率也会小很多
Tip:正常情况下,优先使用 DaemonSet 的方式采集日志,如果单个Pod日志量特别大,超过一般 Agent 发送吞吐量,可以单独对该 Pod 使用 Sidecar 的方式采集日志。
2.3 采集方式
DaemonSet + Stdout
root@master0:/var/log/pods#tree.|--6687e53201c01e3fad31e7d72fbb92a6|`--kube-apiserver||--865.log->/var/lib/docker/containers/3a35ae0a1d0b26455fbd9b267cd9d6ac3fbd3f0b12ee03b4b22b80dc5a1cde03/3a35ae0a1d0b26455fbd9b267cd9d6ac3fbd3f0b12ee03b4b22b80dc5a1cde03-json.log|`--866.log->/var/lib/docker/containers/15a6924f14fcbf15dd37d1c185c5b95154fa2c5f3de9513204b1066bbe474662/15a6924f14fcbf15dd37d1c185c5b95154fa2c5f3de9513204b1066bbe474662-json.log|--a1083c6d-3b12-11ea-9af1-fa163e28f309|`--kube-proxy||--3.log->/var/lib/docker/containers/4b63b5a90a8f9ca6b6f20b49b5ab2564f92df21a5590f46de2a46b031e55c80e/4b63b5a90a8f9ca6b6f20b49b5ab2564f92df21a5590f46de2a46b031e55c80e-json.log|`--4.log->/var/lib/docker/containers/fc7c315d33935887ca3479a38cfca4cca66fad782b8a120c548ad0b9f0ff7207/fc7c315d33935887ca3479a38cfca4cca66fad782b8a120c548ad0b9f0ff7207-json.log
/var/log/pods/<namespace>_<pod_name>_<pod_id>/<container_name>/<num>.log
的形式。root@master-0:/var/log/pods#tree.|--kube-system_kube-apiserver-kind-control-plane_bd1c21fe1f0ef615e0b5e41299f1be61|`--kube-apiserver|`--0.log|--kube-system_kube-proxy-gcrfq_f07260b8-6055-4c19-9491-4a825579528f|`--kube-proxy|`--0.log`--loggie_loggie-csd4g_f1cc32e9-1002-4e64-bd58-fc6094394e06`--loggie`--0.log
/var/log/pod.log
去通配日志文件,采集节点上所有的容器标准输出。Tip:这也是我最初的解决方案,想到这就脸红!
-
无法注入更多元信息比如一些pod的label/env等,特别是在k8s1.14版本之前,甚至无法在采集的 path 里获取到 namespace/pod等信息 -
很难针对单个服务配置特殊的配置,比如某个文件需要使用特殊的多行日志采集,需要配置适合服务自身的日志格式切分等 -
会采集很多不必要的容器日志,造成采集、传输、存储压力
DaemonSet + 日志文件
(1) emtpyDir
emtpyDir 的生命周期跟随Pod,Pod销毁后其中存储的日志也会消失。
-
优点 : 使用简单,不同Pod都使用自己的emtpyDir,有一定的隔离性。
-
缺点 : 日志如果采集不及时,在Pod消耗后,存在丢失的可能性。
-
使用 emptyDir 挂载的日志文件,一般在节点的路径如下: /var/lib/kubelet/pods/${pod.UID}/volumes/kubernetes.io~empty-dir/${volumeName}
(2) hostPath
生命周期和Pod无关,Pod迁移或者销毁,日志文件还保留在现有磁盘上。
-
优点: 生命周期和Pod无关,即使Pod销毁,日志文件依然在节点磁盘上,假设Agent没有采集日志,仍然可以找到日志文件 -
缺点: 默认无隔离性,需要控制挂载的日志路径;另外,Pod迁移节点后,残留的日志文件长期积累容易占据磁盘,同时日志占据的磁盘无法控制使用的配额
为了解决隔离性,避免多个Pod打印日志到相同的路径和文件中,我们需要使用 subPathExpr 字段从 Downward API 环境变量构造 subPath 目录名。该 VolumeSubpathEnvExpansion 功能从 Kubernetes1.15 开始默认开启,在1.17 GA。
(3) Pv
Pv的访问模式包括:
-
ReadWriteOnce(RWO): 读写权限,并且只能被单个Node挂载。
-
ReadOnlyMany(ROX): 只读权限,允许被多个Node挂载。
-
ReadWriteMany(RWX): 读写权限,允许被多个Node挂载。
对于大部分的业务来说,都是Deployment无状态部署,需要挂载同一个Pv共享;对于一些中间件等有状态服务,一般会使用StatefulSet部署,每个Pod会使用独立的Pv。
-
优点: 存储日志不容易丢失; -
缺点: 有一定的使用和运维复杂度;多个Pod共享同一个Pv时存在隔离性问题;很多的日志Agent对采集云盘上的日志文件支持不够成熟,可能存在一些隐患;
另外,鉴于一些Agent对采集docker stdout有一定的支持,所以还存在一些使用上变种,比如利用webhook注入一个sidecar,读取Pod里的日志文件,转换成sidecar的stdout,然后采集sidecar的stdout日志,这里不再详述。