1 背景

石墨文档全部应用部署在Kubernetes上,每时每刻都会有大量的日志输出,我们之前主要使用SLSES作为日志存储。但是我们在使用这些组件的时候,发现了一些问题。

  • 成本问题:

    • SLS个人觉得是一个非常优秀的产品,速度快,交互方便,但是SLS索引成本比较贵

    • 我们想减少SLS索引成本的时候,发现云厂商并不支持分析单个索引的成本,导致我们无法知道是哪些索引构建的不够合理

    • ES使用的存储非常多,并且耗费大量的内存

  • 通用问题:

    • 如果业务是混合云架构,或者业务形态有SAAS和私有化两种方式,那么SLS并不能通用

    • 日志和链路,需要用两套云产品,不是很方便

  • 精确度问题:SLS存储的精度只能到秒,但我们实际日志精度到毫秒,如果日志里面有traceidSLS中无法通过根据traceid信息,将日志根据毫秒时间做排序,不利于排查错误

我们经过一番调研后,发现使用Clickhouse能够很好的解决以上问题,并且Clickhouse省存储空间,非常省钱,所以我们选择了Clickhouse方案存储日志。但当我们深入研究后,Clickhouse作为日志存储有许多落地的细节,但业界并没有很好阐述相关Clickhouse采集日志的整套流程,以及没有一款优秀的Clickhouse日志查询工具帮助分析日志,为此我们写了一套Clickhouse日志系统贡献给开源社区,并将Clickhouse的日志采集架构的经验做了总结。先上个Clickhouse日志查询界面,让大家感受下石墨最懂前端的后端程序员。

2 架构原理图

我们将日志系统分为四个部分:日志采集、日志传输、日志存储、日志管理。

  • 日志采集:LogCollector采用Daemonset方式部署,将宿主机日志目录挂载到LogCollector的容器内,LogCollector通过挂载的目录能够采集到应用日志、系统日志、K8S审计日志等

  • 日志传输:通过不同Logstore映射到Kafka中不同的Topic,将不同数据结构的日志做了分离

  • 日志存储:使用Clickhouse中的两种引擎数据表和物化视图

  • 日志管理:开源的Mogo系统,能够查询日志,设置日志索引,设置LogCollector配置,设置Clickhouse表,设置报警等

以下我们按照这四大部分,阐述其中的架构原理

3 日志采集

3.1 采集方式

Kubernetes容器内日志收集的方式通常有以下三种方案

  • DaemonSet方式采集:在每个 node 节点上部署LogCollector,并将宿主机的目录挂载为容器的日志目录,LogCollector读取日志内容,采集到日志中心。

  • 网络方式采集:通过应用的日志 SDK,直接将日志内容采集到日志中心 。

  • SideCar方式采集:在每个 pod 内部署LogCollectorLogCollector只读取这个 pod 内的日志内容,采集到日志中心。

以下是三种采集方式的优缺点:

DaemonSet方式 网络方式 SideCar方式
采集日志类型 标准输出+文件 应用日志 文件
部署运维 一般,维护DaemonSet 低,维护配置文件 较高,每个pod部署sidecar容器
日志分类存储 可通过容器/路径等映射 业务独立配置 每个POD可单独配置,灵活性高
支持集群规模 取决于配置数 无限制 无限制
适用场景 日志分类明确、功能较单一 性能要求极高的场景 大型、混合型、PAAS型集群
性能资源消耗

我们主要采用DaemonSet方式和网络方式采集日志。DaemonSet方式用于ingress、应用日志的采集,网络方式用于大数据日志的采集(可能需要简单的说明下原因)。以下我们主要介绍下DeamonSet方式的采集方式。

3.2 日志输出

从上面的介绍中可以看到,我们的DaemonSet会有两种方式采集日志类型,一种是标准输出,一种是文件。引用元乙的描述:虽然使用 Stdout 打印日志是 Docker 官方推荐的方式,但大家需要注意:这个推荐是基于容器只作为简单应用的场景,实际的业务场景中我们还是建议大家尽可能使用文件的方式,主要的原因有以下几点:

  • Stdout 性能问题,从应用输出 stdout 到服务端,中间会经过好几个流程(例如普遍使用的JSON LogDriver):应用 stdout -> DockerEngine -> LogDriver -> 序列化成 JSON -> 保存到文件 -> Agent 采集文件 -> 解析 JSON -> 上传服务端。整个流程相比文件的额外开销要多很多,在压测时,每秒 10 万行日志输出就会额外占用 DockerEngine 1 个 CPU 核;

  • Stdout 不支持分类,即所有的输出都混在一个流中,无法像文件一样分类输出,通常一个应用中有 AccessLogErrorLogInterfaceLog(调用外部接口的日志)、TraceLog 等,而这些日志的格式、用途不一,如果混在同一个流中将很难采集和分析;

  • Stdout 只支持容器的主程序输出,如果是 daemon/fork 方式运行的程序将无法使用 stdout

  • 文件的 Dump 方式支持各种策略,例如同步/异步写入、缓存大小、文件轮转策略、压缩策略、清除策略等,相对更加灵活。

从这个描述中,我们可以看出在docker中输出文件在采集到日志中心是一个更好的实践。所有日志采集工具都支持采集文件日志方式,但是我们在配置日志采集规则的时候,发现开源的一些日志采集工具,例如fluentbitfilebeatDaemonSet部署下采集文件日志是不支持追加例如podnamespacecontainer_namecontainer_idlabel信息,并且也无法通过这些label做些定制化的日志采集。

agent类型 采集方式 daemonset部署 sidecar部署
ilogtail 文件日志 能够追加label信息,能够根据label过滤采集 能够追加label信息,能够根据label过滤采集
fluentbit 文件日志 无法追加label信息,无法根据label过滤采集 能够追加abel信息,能够根据label过滤采集
filebeat 文件日志 无法追加label信息,无法根据label过滤采集 能够追加label信息,能够根据label过滤采集
ilogtail 标准输出 能够追加label信息,能够根据label过滤采集 能够追加label信息,能够根据label过滤采集
fluentbit 标准输出 能够追加label信息,能够根据label过滤采集 能够追加abel信息,能够根据label过滤采集
filebeat 标准输出 能够追加label信息,能够根据label过滤采集 能够追加label信息,能够根据label过滤采集

基于无法追加label信息的原因,我们暂时放弃了DeamonSet部署下文件日志采集方式,采用的是基于DeamonSet部署下标准输出的采集方式。

3.3 日志目录

以下列举了日志目录的基本情况

目录 描述 类型
/var/log/containers 存放的是软链接,软链到/var/log/pods里的标准输出日志 标准输出
/var/log/pods 存放标准输出日志 标准输出
/var/log/kubernetes/ master存放Kubernetes 审计输出日志 标准输出
/var/lib/docker/overlay2 存放应用日志文件信息 文件日志
/var/run 获取docker.sock,用于docker通信 文件日志
/var/lib/docker/containers 用于存储容器信息 两种都需要

因为我们采集日志是使用的标准输出模式,所以根据上表我们的LogCollector只需要挂载/var/log/var/lib/docker/containers两个目录。

3.3.1 标准输出日志目录

应用的标准输出日志存储在/var/log/containers目录下,文件名是按照K8S日志规范生成的。这里以nginx-ingress的日志作为一个示例。我们通过ls /var/log/containers/ | grep nginx-ingress指令,可以看到nginx-ingress的文件名。

nginx-ingress-controller-mt2wx_kube-system_nginx-ingress-controller-be3741043eca1621ec4415fd87546b1beb29480ac74ab1cdd9f52003cf4abf0a.log

我们参照K8S日志的规范:/var/log/containers/%{DATA:pod_name}_%{DATA:namespace}_%{GREEDYDATA:container_name}-%{DATA:container_id}.log。可以将nginx-ingress日志解析为:

  • pod_name:nginx-ingress-controller-mt2w

  • namespace:kube-system

  • container_name:nginx-ingress-controller

  • container_id:be3741043eca1621ec4415fd87546b1beb29480ac74ab1cdd9f52003cf4abf0a

通过以上的日志解析信息,我们的LogCollector 就可以很方便的追加podnamespacecontainer_namecontainer_id的信息。

3.3.2 容器信息目录

应用的容器信息存储在/var/lib/docker/containers目录下,目录下的每一个文件夹为容器ID,我们可以通过cat config.v2.json获取应用的docker基本信息。

3.4 LogCollector采集日志

3.4.1 配置

我们LogCollector采用的是fluent-bit,该工具是cncf旗下的,能够更好的与云原生相结合。通过Mogo系统可以选择Kubernetes集群,很方便的设置fluent-bit configmap的配置规则。

3.4.2 数据结构

fluent-bit的默认采集数据结构

  • @timestamp字段:string or float,用于记录采集日志的时间

  • log字段:string,用于记录日志的完整内容

Clickhouse如果使用@timestamp的时候,因为里面有@特殊字符,会处理的有问题。所以我们在处理fluent-bit的采集数据结构,会做一些映射关系,并且规定双下划线为Mogo系统日志索引,避免和业务日志的索引冲突。

  • _time_字段:string or float,用于记录采集日志的时间

  • _log_字段:string,用于记录日志的完整内容

例如你的日志记录的是{"id":1},那么实际fluent-bit采集的日志会是{"_time_":"2022-01-15...","_log_":"{\"id\":1}" 该日志结构会直接写入到kafka中,Mogo系统会根据这两个字段_time__log_设置clickhouse中的数据表。

3.4.3 采集

如果我们要采集ingress日志,我们需要在input配置里,设置ingress的日志目录,fluent-bit会把ingress日志采集到内存里。

然后我们在filter配置里,将log改写为_log_

然后我们在ouput配置里,将追加的日志采集时间设置为_time_,设置好日志写入的kafka borkerskafka topics,那么fluent-bit里内存的日志就会写入到kafka

日志写入到Kafka_log_需要为json,如果你的应用写入的日志不是json,那么你就需要根据fluent-bitparser文档,调整你的日志写入的数据结构:https://docs.fluentbit.io/manual/pipeline/filters/parser

4 日志传输

Kafka主要用于日志传输。上文说到我们使用fluent-bit采集日志的默认数据结构,在下图kafka工具中我们可以看到日志采集的内容。

在日志采集过程中,会由于不用业务日志字段不一致,解析方式是不一样的。所以我们在日志传输阶段,需要将不同数据结构的日志,创建不同的Clickhouse表,映射到Kafka不同的Topic。这里以ingress为例,那么我们在Clickhouse中需要创建一个ingress_stdout_stream的Kafka引擎表,然后映射到Kafkaingress-stdout Topic里。

5 日志存储

我们会使用三种表,用于存储一种业务类型的日志。

  • Kafka引擎表:将数据从Kafka采集到Clickhouseingress_stdout_stream数据表中

create table logger.ingress_stdout_stream
(
_source_ String,
_pod_name_ String,
_namespace_ String,
_node_name_ String,
_container_name_ String,
_cluster_ String,
_log_agent_ String,
_node_ip_ String,
_time_ Float64,
_log_ String
)
engine = Kafka SETTINGS kafka_broker_list = 'kafka:9092', kafka_topic_list = 'ingress-stdout', kafka_group_name = 'logger_ingress_stdout', kafka_format = 'JSONEachRow', kafka_num_consumers = 1;
  • 物化视图:将数据从ingress_stdout_stream数据表读取出来,_log_根据Mogo配置的索引,提取字段在写入到ingress_stdout结果表里

CREATE MATERIALIZED VIEW logger.ingress_stdout_view TO logger.ingress_stdout AS
SELECT
toDateTime(toInt64(_time_)) AS _time_second_,
fromUnixTimestamp64Nano(toInt64(_time_*1000000000),'Asia/Shanghai') AS _time_nanosecond_,
_pod_name_,
_namespace_,
_node_name_,
_container_name_,
_cluster_,
_log_agent_,
_node_ip_,
_source_,
_log_ AS _raw_log_,JSONExtractInt(_log_, 'status') AS status,JSONExtractString(_log_, 'url') AS url
FROM logger.ingress_stdout_stream where 1=1;
  • 结果表:存储最终的数据

    create table logger.ingress_stdout
    (
    _time_second_ DateTime,
    _time_nanosecond_ DateTime64(9, 'Asia/Shanghai'),
    _source_ String,
    _cluster_ String,
    _log_agent_ String,
    _namespace_ String,
    _node_name_ String,
    _node_ip_ String,
    _container_name_ String,
    _pod_name_ String,
    _raw_log_ String,
    status Nullable(Int64),
    url Nullable(String),
    )
    engine = MergeTree PARTITION BY toYYYYMMDD(_time_second_)
    ORDER BY _time_second_
    TTL toDateTime(_time_second_) + INTERVAL 7 DAY
    SETTINGS index_granularity = 8192;

6 总结流程

  • 日志会通过fluent-bit的规则采集到kafka,在这里我们会将日志采集到两个字段里

    • _time_字段用于存储fluent-bit采集的时间

    • _log_字段用于存放原始日志

  • 通过mogo,在clickhouse里设置了三个表

    • app_stdout_stream:将数据从Kafka采集到ClickhouseKafka引擎表

    • app_stdout_view:视图表用于存放mogo设置的索引规则

    • app_stdout:根据app_stdout_view索引解析规则,消费app_stdout_stream里的数据,存放于app_stdout结果表中

  • 最后mogoUI界面,根据app_stdout的数据,查询日志信息

7 Mogo界面展示

  • 查询日志界面

  • 设置日志采集配置界面

以上文档描述是针对石墨Kubernetes的日志采集,想了解物理机采集日志方案的,可以在下文中找到《Mogo使用文档》的链接,运行docker-compose体验Mogo 全部流程,查询Clickhouse日志。限于篇幅有限,Mogo的日志报警功能,下次在讲解。

8 资料

  • github地址: https://github.com/shimohq/mogo

  • Mogo文档:https://mogo.shimo.im

  • Mogo使用文档:https://mogo.shimo.im/doc/AV62KU4AABMRQ

  • fluent-bit文档:https://docs.fluentbit.io/

  • K8S日志

    • 6 个 K8S 日志系统建设中的典型问题,你遇到过几个:https://developer.aliyun.com/article/718735

    • 一文看懂 K8S 日志系统设计和实践:https://developer.aliyun.com/article/727594

    • 9 个技巧,解决 K8S 中的日志输出问题:https://developer.aliyun.com/article/747821

    • 直击痛点,详解 K8S 日志采集最佳实践:https://developer.aliyun.com/article/749468?spm=a2c6h.14164896.0.0.24031164UoPfIX

  • Clickhouse

    • Clickhouse官方文档:https://clickhouse.com/

    • Clickhouse作为Kubernetes日志管理解决方案中的存储:http://dockone.io/article/9356

    • Uber 如何使用 ClickHouse 建立快速可靠且与模式无关的日志分析平台?:https://www.infoq.cn/article/l4thjgnr7hxpkgpmw6dz

    • 干货 | 携程ClickHouse日志分析实践:https://mp.weixin.qq.com/s/IjOWAPOJXANRQqRAMWXmaw

    • 为什么我们要从ES迁移到ClickHouse:https://mp.weixin.qq.com/s/l4RgNQPxvdNIqx52LEgBnQ

    • ClickHouse 在日志存储与分析方面作为 ElasticSearch 和 MySQL 的替代方案:https://mp.weixin.qq.com/s/nJXorcgi0QfXPCKr_HdUZg

    • 快手、携程等公司转战到 ClickHouse,ES 难道不行了?:https://mp.weixin.qq.com/s/hP0ocT-cBCeIl9n1wL_HBg

    • 日志分析下ES/ClickHouse/Loki比较与思考:https://mp.weixin.qq.com/s/n2I94X6tz2jOABzl1djxYg

还在用ES查日志吗,快看看石墨文档 Clickhouse 日志架构玩法相关推荐

  1. 还在担心快应用没流量?全场景新玩法来袭!

    2019年3月20日,TEF·2019快应用开发者大会在北京圆满收官落下帷幕.本次大会宣布中国移动终端公司.海信手机加入快应用生态平台,并介绍了快应用生态潜力.快游戏布局.智能场景新玩法.第三方服务战 ...

  2. atitit.设计文档---操作日志的实现

    atitit.设计文档---操作日志的实现 日志查询 1 ----mybatis  配置... 1 添加日志 1 日志查询 <a class="l-link" href=&q ...

  3. 日志10.24整合项目文档并运行+周报10.18~22工作总结

    日志10.24整合项目文档并运行 一. 安装pnpm,更新代码与远程同步,启动后端itms-server.前端itms-webapp项目 1.安装pnpm:已管理员身份运行命令行,跳转到前端文件夹it ...

  4. 快易需求文档编辑系统(二期)第一次迭代开发总结

    设想和目标 1.目的: 项目为"快易需求文档智能生成系统".软件需求文档是软件开发与维护的重要基础,本项目希望通过建立一个专业的需求文档编辑系统,为软件开发人员提供一个便捷的协作文 ...

  5. Elasticsearch——使用Java API实现ES中的索引、映射、文档操作

    文章目录: 1.开篇 2.案例详解 2.1 创建ES客户端:完成与ES服务端的连接 2.2 创建索引 2.3 查看索引 2.4 删除索引 2.5 创建文档 2.6 修改文档 2.7 查看文档 2.8 ...

  6. ES 5.x bulk update重复的文档ID性能低下分析

    目前很多公司将ES作为数据库数据的索引,将多个数据库的数据同步到ES是非常常见的应用场景.所以感觉问题可能会困扰不止一个用户,而官方的文档没有对update底层机制作了详细的说明,特将该问题整理成文章 ...

  7. ES 5.x Bulk update重复的文档id性能低下

    现在很多公司(包括我们自己)将ES用作数据库数据的索引,将多个数据库的数据同步到ES是非常常见的应用场景.所以感觉这个问题可能会困扰不止一个用户,而官方的文档也没有对update的底层机制及局限做特别 ...

  8. 华为日志服务器文档,免费日志服务器

    免费日志服务器 内容精选 换一换 主机和云服务的日志数据,不方便查阅并且会定期清空.云日志服务采集日志后,日志数据可以在云日志控制台以简单有序的方式展示.方便快捷的方式进行查询,并且可以长期存储.对采 ...

  9. webpack 编译stylus_webpack 文档更新日志(9.129.29)

    久等了,本应上周五发的更新日志,现在才来.话不多说,开始正题. 英文篇 内容更新 API loader 中移除了对 this.exec 的说明 module-methods 中新增了 webpack ...

  10. es数据频繁的更新_es之文档更新过程中并发冲突问题

    1:乐观锁控制 ES是分布式的,也是异步并发的,我们的复制请求是并行发送的:这就意味着请求到达目的地的顺序是不可控制的,是乱序的: 如果是乱序的方式,很有可能出现这样的一个问题,新version的文档 ...

最新文章

  1. mongodb AND查询遇到多个index时候可能会做交集——和复合索引不同
  2. sudo apt install ...
  3. ajax asp后台获取不到post数据,jQuery AJAX调用将数据发布到ASP.Net页面(不是Get但POST)...
  4. Java之递归遍历目录,修改指定文件的指定内容
  5. vim 下的 ex 指令(底行命令模式下)
  6. MATLAB矩阵复制数据
  7. ARM 移植 PPPD
  8. java组件是什么意思_年前面试京东3面凉经~ 面试过程与真题全分享+备战春招(java)...
  9. 后端http缓存策略
  10. 机器视觉牛人及其相关领域分类科普
  11. 2018服务器 芯片组,2018最新主板知识详解,详谈DIY(主板篇)
  12. cas 4.0 mysql 验证_在Ja-sig CAS中使用MySQL数据库进行身份验证
  13. 修改mysql的authen_MySQL数据库出现Authentication plugin怎么办
  14. 高斯(Gaussian)拟合的实现
  15. 机器学习的transformer
  16. cortex m3 开源_开源增强现实耳机,Steam的125M有效帐户等
  17. C语言数据结构-实验一 C语言回顾及算法分析
  18. 修改虚拟机的ip地址步骤
  19. while循环的练习
  20. 什么是IA32 及字长的理解

热门文章

  1. 网页游戏对java的技术要求_网页制作谈谈什么技术是Java开发网页游戏的必要条件呢?怎样在微信公众平台上制作5级游戏?...
  2. HDS AMS 更换控制器和控制器缓存
  3. 中奖人js滚动效果_H5实现中奖记录逐行滚动切换效果
  4. 网站安全性之js注入
  5. java.lang.SecurityException: com.example.rxtest was not granted this permission: android.permission
  6. matlab 均生函数,Matlab 的fspecial函数用法
  7. FlashFXP,FlashFXP的详细介绍
  8. button图片与文字的布局
  9. 自定义ViewPager实现3D画廊效果
  10. 视网膜屏幕和高清视网膜屏幕