K8S生产环境下的大规模日志收集实践
一、项目需求
公司各业务线的项目在从传统的虚拟机或物理机基础设施向云原生迁移时,反馈最多的两个问题:CI/CD和日志信息存储。
容器和Kubernetes的基本原理就决定了,一旦Pod重启,应用程序以前输出的日志信息就没有了。这导致很多线上故障的问题定位非常麻烦,因此各业务也是采取了各种五花八门的解决办法。
基础架构部的职责是需要结合公司目前现状,为这类问题提供统一的解决方案。
从业务方、运维团队等各方反馈的需求来看,要在K8S生产环境中进行大规模的日志收集,需要解决以下几点需求:
- 业务方要求应用程序只需要将日志信息按要求进行输出,至于内容如何被收集,如何存储,他们并不想投入过多的精力关注;
- 运维提出在控制费用成本的大原则下,日志收集组件的资源使用率要尽可能低;
- DBA要求输出到ElasticSearch的数据要使用JSON格式,并且Kafka的Topic和ElasticSearch的Index不能太多,否则不好管理。
二、方案设计
目前我们在传统虚拟机环境中采用的是应用把日志信息输出到本地日志文件,然后通过fluent-bit采集解析,再汇聚到Kafka里,最后由LogStash写入ElasticSearch存储的方案。
为简化方案,最大化利用现有资源,可以继续沿用该方案,只是在采集解析阶段,根据K8S环境的特点,做适应性改造。
因此整个日志监控方案的重点,就只需要解决日志的输出和采集、解析即可。
2.1、常见K8S日志收集策略
在K8S环境下,大体上有两种日志收集策略:
Sidecar方式Node方式
Sidecar方式
SideCar方式即在每个Pod中再添加一个附属的日志收集容器,日志收集容器与业务容器间通过共享磁盘的方式来实现日志文件的共享访问。
其优点如下:
- 自由度高,可以为每个Pod定制单独的日志收集策略
其缺点如下:
- 浪费资源,每个Pod都要增加一个容器,在生产环境大规模部署的情况下,资源的消耗是个不小的问题;
- 容器间容易互相影响,如果日志容器宕机,也会直接影响业务容器
Node方式
Node方式即在每个K8S节点上部署一个全局的日志收集实例,采集当前节点下的所有Pod中的日志信息
其优点如下:
- 占用资源小
- 日志收集实例不会影响到业务容器的状态
其缺点如下:
- 不便于为某个Pod单独定制化日志收集策略
综合考虑,我们还是选择了Node方式,因为其缺点我们可以通过规范化日志输出格式来尽量规避,毕竟需要单独定制化日志收集策略的情况并不多,即使有这种需求,也可以在特殊情况下采用SideCar方式解决。
2.2、日志收集工具
然后对日志采集工具进行选型。
目前我们常用的日志采集工具有:
- fluent-bit
- fluentd
- filebeat
- logstash
- log-pilot
- 。。。
简单对比各工具的优缺点:
LogStash
传统ELK方案里的日志采集器,功能强大,但资源消耗也巨大。
Filebeat
Elastic家族,在LogStash外提供的轻量级采集工具。资源消耗相比LogStash减少很多。但Filebeat不提供日志内容解析的功能,如果需要解析的话,需要在下游的LogStash中进行解析,或在ElasticSearch开启ingest特性。
Fluentd
近两年开始流行起来的新工具,克服了LogStash的许多缺点,资源消耗小。
Fluent-bit
fluent-bit是fluentd的附属项目,fluent-bit比fluentd更轻量,都提供解析功能,并且统一输出为JSON格式。该工具在我们的虚拟机环境已批量部署,并经受住了考验;
log-pilot
阿里云开源的一个项目,专门针对K8S环境而设计,其底层实际进行日志采集的核心功能,其实就是基于Filebeat和fluentd来实现的。
综合比较后,我们选择了采用log-pilot。
2.3、 log-pilot 工作原理
log-pilot会以DaemonSet的形式运行在K8S集群中,基于DaemonSet的特性,每一个Worker节点上,log-pilot会以Pod的形式存在,有且仅会有一个实例运行。
该Pod有如下关键资源:
- 该Pod会将Worker节点的
/目录挂载到Pod里面的/host目录下,这样log-pilot的Pod里面就能够访问该Worker节点上的所有文件 - 该Pod里面,会运行一个fluentd或者filebeat进程,执行具体的日志采集策略
为实现容器中日志文件的采集,log-pilot的Pod做如下一些事情:
- 首先会通过docker api列出当前节点中的所有容器;
- 其次会监听当前节点的docker事件,当出现
start或者restart事件时,会从事件中获取容器ID - 通过
docker inspect获取容器信息。主要获取容器的磁盘挂载信息和环境变量。 - 通过结合环境变量传入的信息,和磁盘的挂载信息,在当前Pod的
/host找到相应的日志文件 - log-pilot根据以上信息,生成Filebeat或者Fluentd的配置文件
- 通过发送信号,重启filebeat或者fluentd,加载新的采集策略,从而实现新容器的日志采集
三、最终方案
3.1 方案概述
整个流程如下:

访方案在K8S集群中部署一个log-pilot的DaemonSet,利用daemonset的特性,会保证每一个k8s所有节点上会自动部署仅仅一个log-pilot,而log-pilot则负责当前节点的所有日志收集工作。
在log-pilot内部则选择采用fluentd的实现方式。
在每一个k8s节点上,存储日志文件的磁盘,通过LVM的方式进行管理,并挂载到/logs1的目录,以实现磁盘空间的动态扩容。
而每一个线上业务,如果需要收集日志,则其Daemonset、Deployment需要将节点上的/logs1,通过hostPath的方式挂载到Pod上,Pod里面的进程需要将日志文件写入到挂载的目录中。
3.2 方案优缺点
访方案的优缺点:
优点:
- 降低成本,不必创建额外的日志收集容器,一个Worker节点只会存在一个收集程序,且内存、CPU等资源消耗都非常小;
- 业务无干扰,业务团队无需花费过多的精力去关注日志收集的具体实现,应用如果需要日志收集功能,只需要简单的在Deployment中添加几个声明,通过环境变量传入几个参数即可;
缺点:
- 不灵活,无法针对某些日志进行特定操作,比如修改某个字段的数据类型
- 一旦日志采集程序出异常,将会影响当前节点的所有Pod的日志采集
- Log-Pilot如果需要大的调整,影响面会很大,且不容易升级
3.3 相关规范
3.3.1 日志文件规范
为了能够保证log-pilot正常工作,在日志输出与采集的过程中需要遵循如下规范。规范主要分为三大类:
日志路径规范
在本方案中,因为k8s上的所有Pod都会将日志写到Worker节点的/logs1目录下,为了避免Pod间的日志相互影响,所以日志的存储路径有如下的规范:
1 | /data/logs/{team}/{project}/{tier}-{pod名字}.log |
路径中各参数含义说明:
- team,团队名,英文名
- project,项目名,英文名
- tier,模块名,英文名
- Pod名字
日志清理规范
磁盘空间的清理,需要每个业务线根据业务特点和申请的磁盘大小,设定磁盘清理策略。
3.3.2 日志解析规范
每行日志信息,必须按字段解析,并以JSON格式输出到Kafka
3.3.3 Kafka Topic规范
Kafka的Topic命名,应该遵循团队名+项目名+模块/功能名的规则。
比如:qbus-message-log
3.3.4 ElasticSearch 索引规范
- ElasticSearch的索引前缀应与
Kafka的主题名保持一致; - 默认按天新建索引。现在不支持自定义。
ElasticSearch中的每一条记录的@timestamp字段,默认为日志进入ElasticSearch的时间,如果需要将日志中的时间作为@timestamp字段,那么日志中的时间字段必须为timestamp,其格式为时间戳格式;
4、log-pilot 部署
磁盘挂载
为保证日志文件的存储,需要为每个Worker节点添加单独的磁盘空间,具体容量可根据当前业务容量来规划计算。
增加的磁盘以LVM的方式管理,并挂载到/logs1目录
log-pilot 部署
在本方案中使用了log-pilot v0.9.5版,并根据公司实际情况,做了相应定制,编译后重新制作的Docker镜像已Push到了公司的镜像仓库中。
在k8s集群中创建log-pilot服务,由于线上K8S环境启用了容忍与污点机制,所以需要相应的容忍配置。
具体的配置可参考一下YAML内容:
1 | apiVersion: extensions/v1beta1 |
5、应用接入
5.1、调整日志输出格式
Java、PHP、Golang等各类应用的日志信息输出格式,此前已有规范,只要参照已有规范,即可正确进行解析;
5.2、调整日志文件名规则
将日志按/data/logs/{team}/{project}/{tier}/{pod名字}/*.log的命名规范,输出到Pod里面的/data/logs目录下。
5.3、日志采集后端调整
Kafka配置
线上的ckafka(vpc-ckafka-mid.kn.com)中创建一个Topic,命名参照方案中规范;
LogStash配置
LogStash增加对新增Topic的采集规则,并将此主题里面的消息,同步到线上的es中,并且需要将timestamp字段转换成es中的@timestamp。(timestamp的单位为秒)
5.4、应用Pod配置
Pod中挂载日志存储目录
将Worker节点的/logs1以host方式挂载到,Pod里面的/data/logs目录。
可参考如下YAML配置:
1
2
3
4- name: logs
hostPath:
path: /logs1
type: Directory
环境变量调整
此前CI/CD平台已预制了各种Helm Chart模板,只需要结合业务情况为模板中对应环境变量赋值即可。
通过Deployment或者StatefulSet的helm模板环境变量传入日志路径及解析相关信息
aliyun_logs_$name$name可以自定义,$name不能含有下划线’_’,用于声明日志- value 可以是 “stdout” ,或者为容器内日志文件的绝对路径
aliyun_logs_$name.format日志格式,目前支持以下格式- none:无格式纯文本。
- json:json 格式,每行一个完整的 json 字符串。推荐使用。
- regexp:正则,需要再添加一个环境变量,key为
aliyun_logs_$name.format.pattern,value为就是正则表达式,可以在https://rubular.com/上进行验证
aliyun_logs_$name.tags,收集日志时,额外增加的字段,格式为 k1=v1,k2=v2,每个 key-value 之间使用逗号分隔,例如aliyun_logs_access.tags="name=hello,stage=test",每一个日志,至少需要在添加如下几个标签:- team,团队名
- project,项目名
- tier,模块名
- app,应用名
- env,环境,例如prod,test,stage,dev
aliyun_logs_$name.target,用于指定日志写入的ckafka主题名,实例名不需要配置。aliyun_logs_$name.format.time_key,用于设置日志里面的时间字段aliyun_logs_$name.format.time_type,用于设置日志里面的时间字段类型。主要有三种类型: 1. float: seconds from Epoch + nano seconds (e.g. 1510544836.154709804), 2. unixtime: seconds from Epoch (e.g. 1510544815), 3. string: use format specified by time_format, local time or time zonealiyun_logs_$name.format.time_format,当aliyun_logs_$name.time_type为string时,用于设置时间的格式,其利用的是ruby的strptime函数,其支持的格式选项如下:格式字符 描述 %a The abbreviated weekday name (“Sun”) %A The full weekday name (“Sunday”) %b The abbreviated month name (“Jan”) %B The full month name (“January”) %c The preferred local date and time representation %C Century (20 in 2009) %d Day of the month (01..31) %D Date (%m/%d/%y) %e Day of the month, blank-padded ( 1..31) %F Equivalent to %Y-%m-%d (the ISO 8601 date format) %h Equivalent to %b %H Hour of the day, 24-hour clock (00..23) %I Hour of the day, 12-hour clock (01..12) %j Day of the year (001..366) %k hour, 24-hour clock, blank-padded ( 0..23) %l hour, 12-hour clock, blank-padded ( 0..12) %L Millisecond of the second (000..999) %m Month of the year (01..12) %M Minute of the hour (00..59) %n Newline (n) %N Fractional seconds digits, default is 9 digits (nanosecond) %p Meridian indicator (“AM” or “PM”) %P Meridian indicator (“am” or “pm”) %r time, 12-hour (same as %I:%M:%S %p) %R time, 24-hour (%H:%M) %s Number of seconds since 1970-01-01 00:00:00 UTC. %S Second of the minute (00..60) %t Tab character (t) %T time, 24-hour (%H:%M:%S) %u Day of the week as a decimal, Monday being 1. (1..7) %U Week number of the current year, starting with the first Sunday as the first day of the first week (00..53) %v VMS date (%e-%b-%Y) %V Week number of year according to ISO 8601 (01..53) %W Week number of the current year, starting with the first Monday as the first day of the first week (00..53) %w Day of the week (Sunday is 0, 0..6) %x Preferred representation for the date alone, no time %X Preferred representation for the time alone, no date %y Year without a century (00..99) %Y Year which may include century, if provided %z Time zone as hour offset from UTC (e.g. +0900) %Z Time zone name %% Literal “%” character aliyun_logs_$name.format.timezone,用于设置日志里面时间的时区,eg: +08:00aliyun_logs_$name.format.types,用于转换日志里面的字段类型,格式如下: field1:type, field2:type:option,…,type支持: string、bool、integer、float、time、array; option仅仅针对time和array有效,当time时,用于配置解析格式;当为array时,用于配置分隔符。
5.5、日志检索
应用按如上方式进行部署后,就能直接在kibana上进行查看了。
