容器时代越来越多的传统应用将会逐渐容器化,而日志又是应用的一个关键环节,那么在应用容器化过程中,如何方便快捷高效地来自动发现和采集应用的日志,如何与日志存储系统协同来高效存储和搜索应用日志。本文将主要跟大家分享下如何通过Log-Pilot来采集容器的标准输出日志和容器内文件日志。
日志采集难点
首先我们先看一下容器日志采集的一些难点,这里主要从两个方面来讲,第一个是容器本身的特性,第二个是现有采集工具的一些缺陷:
容器本身特性
采集目标多容器一般推荐将日志写在标准输出,但是也有一些特殊的场景就是应用直接将日志写在容器内部。对于容器的标准输出日志来说,Docker Engine 本身就提供了一个很好的日志采集能力,但是对于容器内部的文件日志采集,现在并没有一个很好的工具能够去动态发现采集。 容器的弹性伸缩性
我们知道 Kubernetes 本身是一个分布式集群,那么我们事先就无法像传统虚拟机环境下那样,事先配置好日志的采集路径等一些信息,因此这对于容器的日志采集来说也将面临一个很大的挑战。
现有采集工具缺陷
缺乏动态配置的能力目前的采集工具都需要我们事先手动配置好日志采集方式和路径等信息,由于它无法能够自动感知到容器的生命周期变化或者动态漂移,所以它无法动态地去配置。 日志采集重复或丢失
现有的一些采集工具基本上是通过 tail 的方式来进行日志采集的,那么这里就可能存在两个方面的问题:一个是可能导致日志丢失,比如采集工具在重启的过程中,而应用依然在写日志,那么就有可能导致这个窗口期的日志丢失;而对于这种情况一般保守的做法就是,默认往前多采集 1M 日志或 2M 的日志,那么这就又会可能引起日志采集重复的问题。 未明确标记日志源
一个应用可能有很多个容器,输出的应用日志也是一样的,那么当我们将所有应用日志收集到统一日志存储后端时,在搜索日志的时候,我们就无法明确这条日志具体是哪一个节点上的哪一个应用容器产生的。
破解利器 Log-Pilot
针对这些问题,我们提供了一个智能容器采集工具 Log-Pilot,它不仅能够高效便捷地将容器日志采集输出到多种存储日志后端,同时还能够动态地发现和采集容器内部的日志文件。
Log-Pilot 本身分为三部分,其中一部分就是容器事件管理,它能够动态地监听容器的事件变化,然后依据容器的标签来进行解析,生成日志采集配置文件,然后交由采集插件来进行日志采集。
下面看一下,针对前面容器日志采集的难点,Log-Pilot 是如何去解决的:
声明式日志配置
Log-Pilot 支持声明式日志配置,可以依据容器的 Label 或者 ENV 来动态地生成日志采集配置文件。这里重点说明两个变量:
name:我们自定义的一个字符串,它在不同的场景下指代不同的含义。当我们将日志采集到 ElasticSearch 的时候, name 表示的是 Index;当我们将日志采集到 Kafka 的时候, name 表示的是 Topic;当我们将日志采集到阿里云日志服务的时候,name 表示的是 LogstoreName。 path:它本身支持两种,一种是约定关键字 stdout,表示的是采集容器的标准输出日志,第二种是容器内部的具体文件日志路径,可以支持通配符的方式。比如我们要采集 tomcat 容器日志,那么我们通过配置标签aliyun.logs.catalina=stdout
来采集 tomcat 标准输出日志,通过配置标签 aliyun.logs.access=/usr/local/tomcat/logs/*.log
来采集 tomcat 容器内部文件日志。
自动发现机制
Log-Pilot 能够自动感知宿主机上容器的创建删除事件,进而动态配置容器日志采集配置文件。
一般情况下我们是通过全量扫描加事件监听的方式,比如采集工具进程在起来的时候,会先去全量扫描一遍宿主机上的所有容器列表,然后依据容器的声明式配置来进行日志采集配置文件的动态生成,然后再注册事件监听,那么这样可能会导致一个问题,在全量扫描配置的过程中并且在注册事件监听之前,这个窗口期的容器事件就有可能会丢失,因此这里我们采用的是先注册事件监听,然后再全量扫描,这样就可以很好地规避容器事件丢失的问题。
句柄保持机制
自动 CheckPoint
Log-Pilot 内部会实时跟踪日志采集偏移量,然后维持日志文件信息与偏移量的映射关系,最后定期地持久化到磁盘中。采用偏移量的方式我们可以避免日志采集丢失和重复的问题,同时即使当采集工具宕掉再起来,它可以通过加载持久化在磁盘上的元数据信息,然后从指定的日志偏移位置上继续采集日志。
句柄保持机制
Log-Pilot 在监测到配置的日志路径目录下有新的日志文件产生时会主动地打开其句柄,并维持打开状态,这样是为了防止因日志采集工具比较慢或者应用日志输出速率特别大,比如说当前已经生成五个日志文件但只采集到第三个,后面两个还没有开始采集,一旦这个容器退出就可能导致后面两个文件的日志丢失了。
因此 Log-Pilot 在监测到有新的日志产生的时候,会立即将其文件句柄打开,这样的话即使这个日志文件删除,它在磁盘中的数据并没有被擦除,只有当该日志文件采集完成后,我们才会主动去释放这个文件句柄,这样就可以保证日志文件里面的日志不会丢失。
自动数据打标
Log-Pilot 在采集容器日志的时候,同时也会收集容器的元数据信息,包括容器的名称,容器所属的服务名称以及容器所属的应用名称,同时在 Kubernetes 里面也会采集容器所属的 Pod 信息,包括 Pod 的名称,Pod 所属的 namespace 以及 Pod 所在的节点信息。这样我们排查问题时,就可以很方便地知道这个日志数据是来源于哪个节点上的哪个应用容器。
支持高级特性
Log-Pilot 除了提供前面的几个特性外,还支持一些其他的高级特性,比如低资源消耗,支持自定义 tag,支持多种日志解析格式,支持自定义日志输出 target 以及支持 fluentd 和 filebeat 等插件,最后支持对接到多种日志存储后端。
低资源消耗
针对低资源消耗,我们先简单看一下容器日志采集一般采用的两种部署模式:
SideCar 模式这种需要我们在每个 Pod 中都附带一个 logging 容器来进行本 Pod 内部容器的日志采集,一般采用共享卷的方式,但是对于这一种模式来说,很明显的一个问题就是占用的资源比较多,尤其是在集群规模比较大的情况下,或者说单个节点上容器特别多的情况下,它会占用过多的系统资源,同时也对日志存储后端占用过多的连接数。当我们的集群规模越大,这种部署模式引发的潜在问题就越大。
Node 模式这种模式是我们在每个 Node 节点上仅需布署一个 logging 容器来进行本 Node 所有容器的日志采集。这样跟前面的模式相比最明显的优势就是占用资源比较少,同样在集群规模比较大的情况下表现出的优势越明显。
但对于这种模式来说我们就需要一个更加智能的日志采集工具来配合,那么这里用 Log-Pilot 工具就是一个很好的选择,因此我们在布署 Log-Pilot 采集工具的时候采用的就是 Node 模式。
支持自定义Tag
Log-Pilot 也支持自定义Tag,我们可以在容器的标签或者环境变量里配置 aliyun.logs.$name.tags: k=v
,那么在采集日志的时候也会将k=v
采集到容器的日志输出中。
比如我们有一种场景,有一个开发环境和测试环境,应用日志都会被采集到统一的一个日志存储后端,假设是一个 ElasticSearch 集群,但是我们在 ElasticSearch 中查询日志的时候又想区分出来,具体某条日志记录到底来源于生产环境,还是测试环境。
那么我们就可以通过给测试环境的容器打上 stage=dev
的 tag,给生产环境的容器打上 stage=pro
的 tag,Log-Pilot 在采集容器日志的时候,同时会将这些 tag 随容器日志一同采集到日志存储后端中,那么当我们在查询日志的时候,就可以通过 stage=dev
或者 stage=pro
能明确地区分出某条日志是来源于生产环境的应用容器所产生,还是测试环境应用容器所产生的。另外通过自定义 tag 的方式我们还可以进行日志统计、日志路由和日志过滤。
支持多种日志解析格式
Log-Pilot 也支持多种日志解析格式,通过 aliyun.logs.$name.format: <format>
标签就可以告诉 Log-Pilot 在采集日志的时候,同时以什么样的格式来解析日志记录。目前主要支持六种:
支持自定义输出Target
这里假设一种场景,我们同时有一个生产环境和一个测试环境,应用日志都需要被采集到同一套 Kafka 中,然后由不同的 consumer 去消费。
但是我们同样希望区分出来,某条日志数据是由生产环境的应用容器产生的,还是测试环境的应用容器产生的,但我们在测试环境中的应用容器已经配置了 aliyun.logs.svc=stdout
标签,那么当这些应用容器的标准输出日志被采集到 kafka 中,它最终会被路由到 topic=svc
的消息队列中,那么订阅了 topic=svc
的 consumer 就能够接收测试环境的应用容器产生的日志。
但当我们将该应用发布到生产环境时,希望它产生的日志只能交由生产环境的 consumer 来接收处理,那么我们就可以通过 target 的方式,给生产环境的应用容器额外定义一个 target=pro-svc
,那么生产环境的应用日志在被采集到 Kafka 中时,最终会被路由到 topic 为 pro-svc 的消息队列中,那么订阅了 topic =pro-svc
的 consumer 就可以正常地接收到来自于生产环境的容器产生的日志。
因此这里的 target 本身也有三种含义:
当我们将日志对接到ElasticeSearch时,这个 target 字符串是 Index; 当我们对接到Kafka时,它指代的是 topic; 当我们将日志对接到日志服务时,它代表的是 Logstore Name。支持多采集插件
目前 Log-Pilot 支持两种采集插件:一个是CNCF社区的Fluentd插件,一个是Elastic的Filebeat插件;其同时其支持对接多种存储后端,目前 Fluentd 和 Filebeat 都支持 Elasticsearch、Kafka、File、Console 作为日志存储后端,而 Fluentd 还支持 Graylog、阿里云日志服务 以及 Mongodb 作为存储后端。
社区支持
Log-Pilot在2017年初已在GitHub开源:https://github.com/AliyunContainerService/log-pilot,本着开源开放的原则,欢迎大家使用和贡献支持,共同将Log-Pilot打造成更加智能更加高效的容器日志采集利器。