日志系统之基于flume收集docker容器日志

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 最近我在日志收集的功能中加入了对docker容器日志的支持。这篇文章简单谈谈策略选择和处理方式。 关于docker的容器日志 docker 我就不多说了,这两年火得发烫。最近我也正在把日志系统的一些组件往docker里部署。

最近我在日志收集的功能中加入了对docker容器日志的支持。这篇文章简单谈谈策略选择和处理方式。

关于docker的容器日志

docker 我就不多说了,这两年火得发烫。最近我也正在把日志系统的一些组件往docker里部署。很显然,组件跑在容器里之后很多东西都会受到容器的制约,比如日志文件就是其中之一。

当一个组件部署到docker中时,你可以通过如下命令在标准输出流(命令行)中查看这个组件的日志:

docker logs ${containerName}

日志形如:

终端查看docker容器日志

但这种方式并不能让你实时获取日志并对它们进行收集。但是docker还是比较友好的,它把这些日志文件都保存在以容器ID为文件名的文件系统中。如果你是标准安装的话,那么它应该在文件系统的如下位置:

/var/lib/docker/containers/${fullContainerId}/${fullContainerId}-json.log

这个fullContainerId应该如何获得呢?简单一点,你可以通过如下命令来查看full container-id:

docker ps --no-trunc

然后通过vi 命令来查看日志文件。但基于文件的日志和基于标准输出流的日志是有区别的,区别是基于文件的日志是json形式的,并且以标准输出流的一行作为日志的间隔。形如:

文件查看docker容器日志

这相当于两层日志格式,外面这一层是docker封装的,格式是固定的;而内层则是因具体的组件而不同的。外面的格式其实对我们而言是无用的,但还是要先解析完外层日志之后,才能回到我们收集组件格式的上下文中来。

如果这是docker给我们日志收集带来的麻烦之一,那么下面还有一个更棘手的问题就是:多行日志的关联性问题。比较常见的一个例子就是程序的异常堆栈(stacktrace)。因为在标准输出流中,这些异常堆栈是分多行输出的,所以在docker日志中一个异常堆栈被以多条日志拆开记录就像上面的示例日志一样。

其实在基于非docker日志文件的日志收集中,我们已经针对以异常堆栈为主的多行关联性日志的收集进行了支持,但现在的一个问题是docker不但把关联性日志拆成多条,而且在外面包裹了自己的格式,导致我们在不解析的情况下根本拿不到真正的日志分隔符,日志分隔符用于区分多行日志内容中真正的日志分隔界限。比如上图示例的log4j日志,我们通过判断行首前缀是否有[,来判断某一行是一条日志的起点还是应该被追加到上一条日志中。

处理方案

客户端不解析

在没有遇到docker容器日志之前,我们遵循的规则是:agent只负责采集,不作任何解析,解析在storm里进行。针对上面这种docker容器的多行关联性日志,在客户端不解析自然没办法识别关联性,那么就只能作逐行收集,然后在服务端解析。如果在服务端解析,就要保证同一个日志文件中日志的顺序性。

  • 基于队列的顺序性

我说的这种队列是日志收集之后暂存在消息中间件中的消息队列。这可以确保日志在解析之前一直保证顺序性,但这样的代价显然是很高的,为了一个节点上的一种日志就要单开一个队列,那么多节点上的多日志类型将会使得消息中间件中的队列快速增多,而性能开销也非常大。并且还有个问题是,单纯保证在消息队列里有序还不够,还必须让消费者(比如storm)的处理逻辑针对这个队列是单一的,如果一个消费者负责多个不同的日志队列,那么还是无法识别单一文件的日志顺序性。但是如果消费者跟日志队列一对一处理,那么像storm这种消费者应对新日志类型的扩展性就会降低。因为storm的实时处理是基于topology的,一个topology既包含输入(spout)也包含输出逻辑。这种情况下每次新增一个日志列队,topology就必须重启一次(为了识别新的spout)。

  • 基于自增序列排序的顺序性

如果不通过外部的数据结构来维持单一日志文件中日志的顺序性,那就只能通过为每个日志添加序列号来标识日志的顺序性。这种方式可以允许日志在消息中间件中无序、混合存储。但它同样存在弊端:

(1)单一的序列号还不足够,还需要额外的标识才能区分同类、不同主机的日志(集群环境)

(2)为了得到前后有关联的日志,日志必须先落数据库,然后借助于排序机制还原原先的顺序,然后按顺序进行合并或者单一处理

上面这两点都比较棘手。

客户端解析docker日志格式

上面分析了客户端不解析存在的问题,另一种做法是客户端解析。因为docker的格式是固定的,这相对省了点事,我们可以选择只做外层解析,也就是对docker容器日志的格式做解析,以此来还原原始日志(注意这里原始日志还是纯文本),而拿到原始日志之后,就可以根据原先的日志分隔符解析多行关联性日志,其他问题也就不存在了。但毫无疑问,这需要对日志采集器进行定制。

flume的定制

flume对日志的读取逻辑组件称之为EventDeserializer,这里我们使用的MultiLineDeserializer是基于LineDeserializer定制的。

首先我们定义一个配置项来标识日志是否是docker产生的:

wrappedByDocker = true

接着,我们根据docker的json格式定义其对应的Java Bean:

    public static class DockerLog {

        private String log;
        private String stream;
        private String time;

        public DockerLog() {
        }

        public String getLog() {
            return log;
        }

        public void setLog(String log) {
            this.log = log;
        }

        public String getStream() {
            return stream;
        }

        public void setStream(String stream) {
            this.stream = stream;
        }

        public String getTime() {
            return time;
        }

        public void setTime(String time) {
            this.time = time;
        }
    }

然后,当我们读取一行之后,如果日志是docker产生的,那么先用gson将其反序列化为java对象,然后取出我们关心的log字段拿到原始日志文本,接下来的处理就跟原来一样了。

readBeforeOffset = in.tell();
String preReadLine = readSingleLine();

if (preReadLine == null) return null;

    //if the log is wrapped by docker log format,
    //should extract origin log firstly
    if (wrappedByDocker) {
        DockerLog dockerLog = GSON.fromJson(preReadLine, DockerLog.class);
        preReadLine = dockerLog.getLog();
    }

这样agent采集到的日志就都是原始日志了,也就保证了后续一致的解析逻辑。

针对flume的完整定制开源在github/flume-customized.


原文发布时间为:2016-02-07
本文作者:vinoYang
本文来自云栖社区合作伙伴CSDN博客,了解相关信息可以关注CSDN博客。
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
20天前
|
数据库 Docker 容器
docker容器为啥会开机自启动
通过配置适当的重启策略,Docker容器可以在主机系统重启后自动启动。这对于保持关键服务的高可用性和自动恢复能力非常有用。选择适合的重启策略(如 `always`或 `unless-stopped`),可以确保应用程序在各种情况下保持运行。理解并配置这些策略是确保Docker容器化应用可靠性的关键。
169 93
|
1月前
|
监控 NoSQL 时序数据库
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
275 77
|
22天前
|
Ubuntu NoSQL Linux
《docker基础篇:3.Docker常用命令》包括帮助启动类命令、镜像命令、有镜像才能创建容器,这是根本前提(下载一个CentOS或者ubuntu镜像演示)、容器命令、小总结
《docker基础篇:3.Docker常用命令》包括帮助启动类命令、镜像命令、有镜像才能创建容器,这是根本前提(下载一个CentOS或者ubuntu镜像演示)、容器命令、小总结
106 6
《docker基础篇:3.Docker常用命令》包括帮助启动类命令、镜像命令、有镜像才能创建容器,这是根本前提(下载一个CentOS或者ubuntu镜像演示)、容器命令、小总结
|
1月前
|
搜索推荐 安全 数据安全/隐私保护
7 个最能提高生产力的 Docker 容器
7 个最能提高生产力的 Docker 容器
123 35
|
21天前
|
数据库 Docker 容器
docker容器为啥会开机自启动
通过配置适当的重启策略,Docker容器可以在主机系统重启后自动启动。这对于保持关键服务的高可用性和自动恢复能力非常有用。选择适合的重启策略(如 `always`或 `unless-stopped`),可以确保应用程序在各种情况下保持运行。理解并配置这些策略是确保Docker容器化应用可靠性的关键。
44 17
|
1月前
|
Ubuntu Linux 开发工具
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈
Docker 是一个开源的容器化平台,允许开发者将应用程序及其依赖项打包成标准化单元(容器),确保在任何支持 Docker 的操作系统上一致运行。容器共享主机内核,提供轻量级、高效的执行环境。本文介绍如何在 Ubuntu 上安装 Docker,并通过简单步骤验证安装成功。后续文章将探讨使用 Docker 部署开源项目。优雅草央千澈 源、安装 Docker 包、验证安装 - 适用场景:开发、测试、生产环境 通过以上步骤,您可以在 Ubuntu 系统上成功安装并运行 Docker,为后续的应用部署打下基础。
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈
|
21天前
|
运维 Java 虚拟化
《docker基础篇:1.Docker简介》,包括Docker是什么、容器与虚拟机比较、能干嘛、去哪下
《docker基础篇:1.Docker简介》,包括Docker是什么、容器与虚拟机比较、能干嘛、去哪下
90 12
|
22天前
|
Kubernetes Linux 虚拟化
入门级容器技术解析:Docker和K8s的区别与关系
本文介绍了容器技术的发展历程及其重要组成部分Docker和Kubernetes。从传统物理机到虚拟机,再到容器化,每一步都旨在更高效地利用服务器资源并简化应用部署。容器技术通过隔离环境、减少依赖冲突和提高可移植性,解决了传统部署方式中的诸多问题。Docker作为容器化平台,专注于创建和管理容器;而Kubernetes则是一个强大的容器编排系统,用于自动化部署、扩展和管理容器化应用。两者相辅相成,共同推动了现代云原生应用的快速发展。
96 11
|
1月前
|
Unix Linux Docker
CentOS停更沉寂,RHEL巨变限制源代:Docker容器化技术的兴起助力操作系统新格局
操作系统是计算机系统的核心软件,管理和控制硬件与软件资源,为用户和应用程序提供高效、安全的运行环境。Linux作为开源、跨平台的操作系统,具有高度可定制性、稳定性和安全性,广泛应用于服务器、云计算、物联网等领域。其发展得益于庞大的社区支持,多种发行版如Ubuntu、Debian、Fedora等满足不同需求。
69 4
|
1月前
|
数据建模 应用服务中间件 nginx
docker替换宿主与容器的映射端口和文件路径
通过正确配置 Docker 的端口和文件路径映射,可以有效地管理容器化应用程序,确保其高效运行和数据持久性。在生产环境中,动态替换映射配置有助于灵活应对各种需求变化。以上方法和步骤提供了一种可靠且易于操作的方案,帮助您轻松管理 Docker 容器的端口和路径映射。
158 3